Fix FullScreen crash in Webapp
[platform/framework/web/chromium-efl.git] / tools / find_runtime_symbols / prepare_symbol_info.py
1 #!/usr/bin/env python
2 # Copyright 2012 The Chromium Authors
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import hashlib
7 import json
8 import logging
9 import optparse
10 import os
11 import re
12 import shutil
13 import subprocess
14 import sys
15 import tempfile
16
17
18 BASE_PATH = os.path.dirname(os.path.abspath(__file__))
19 REDUCE_DEBUGLINE_PATH = os.path.join(BASE_PATH, 'reduce_debugline.py')
20 _TOOLS_LINUX_PATH = os.path.join(BASE_PATH, os.pardir, 'linux')
21 sys.path.insert(0, _TOOLS_LINUX_PATH)
22
23
24 from procfs import ProcMaps  # pylint: disable=F0401
25
26
27 LOGGER = logging.getLogger('prepare_symbol_info')
28
29
30 def _dump_command_result(command, output_dir_path, basename, suffix):
31   handle_out, filename_out = tempfile.mkstemp(
32       suffix=suffix, prefix=basename + '.', dir=output_dir_path)
33   handle_err, filename_err = tempfile.mkstemp(
34       suffix=suffix + '.err', prefix=basename + '.', dir=output_dir_path)
35   error = False
36   try:
37     subprocess.check_call(
38         command, stdout=handle_out, stderr=handle_err, shell=True)
39   except (OSError, subprocess.CalledProcessError):
40     error = True
41   finally:
42     os.close(handle_err)
43     os.close(handle_out)
44
45   if os.path.exists(filename_err):
46     if LOGGER.getEffectiveLevel() <= logging.DEBUG:
47       with open(filename_err, 'r') as f:
48         for line in f:
49           LOGGER.debug(line.rstrip())
50     os.remove(filename_err)
51
52   if os.path.exists(filename_out) and (
53       os.path.getsize(filename_out) == 0 or error):
54     os.remove(filename_out)
55     return None
56
57   if not os.path.exists(filename_out):
58     return None
59
60   return filename_out
61
62
63 def prepare_symbol_info(maps_path,
64                         output_dir_path=None,
65                         alternative_dirs=None,
66                         use_tempdir=False,
67                         use_source_file_name=False):
68   """Prepares (collects) symbol information files for find_runtime_symbols.
69
70   1) If |output_dir_path| is specified, it tries collecting symbol information
71   files in the given directory |output_dir_path|.
72   1-a) If |output_dir_path| doesn't exist, create the directory and use it.
73   1-b) If |output_dir_path| is an empty directory, use it.
74   1-c) If |output_dir_path| is a directory which has 'files.json', assumes that
75        files are already collected and just ignores it.
76   1-d) Otherwise, depends on |use_tempdir|.
77
78   2) If |output_dir_path| is not specified, it tries to create a new directory
79   depending on 'maps_path'.
80
81   If it cannot create a new directory, creates a temporary directory depending
82   on |use_tempdir|.  If |use_tempdir| is False, returns None.
83
84   Args:
85       maps_path: A path to a file which contains '/proc/<pid>/maps'.
86       alternative_dirs: A mapping from a directory '/path/on/target' where the
87           target process runs to a directory '/path/on/host' where the script
88           reads the binary.  Considered to be used for Android binaries.
89       output_dir_path: A path to a directory where files are prepared.
90       use_tempdir: If True, it creates a temporary directory when it cannot
91           create a new directory.
92       use_source_file_name: If True, it adds reduced result of 'readelf -wL'
93           to find source file names.
94
95   Returns:
96       A pair of a path to the prepared directory and a boolean representing
97       if it created a temporary directory or not.
98   """
99   alternative_dirs = alternative_dirs or {}
100   if not output_dir_path:
101     matched = re.match('^(.*)\.maps$', os.path.basename(maps_path))
102     if matched:
103       output_dir_path = matched.group(1) + '.pre'
104   if not output_dir_path:
105     matched = re.match('^/proc/(.*)/maps$', os.path.realpath(maps_path))
106     if matched:
107       output_dir_path = matched.group(1) + '.pre'
108   if not output_dir_path:
109     output_dir_path = os.path.basename(maps_path) + '.pre'
110   # TODO(dmikurube): Find another candidate for output_dir_path.
111
112   used_tempdir = False
113   LOGGER.info('Data for profiling will be collected in "%s".' % output_dir_path)
114   if os.path.exists(output_dir_path):
115     if os.path.isdir(output_dir_path) and not os.listdir(output_dir_path):
116       LOGGER.warn('Using an empty existing directory "%s".' % output_dir_path)
117     else:
118       LOGGER.warn('A file or a directory exists at "%s".' % output_dir_path)
119       if os.path.exists(os.path.join(output_dir_path, 'files.json')):
120         LOGGER.warn('Using the existing directory "%s".' % output_dir_path)
121         return output_dir_path, used_tempdir
122       if use_tempdir:
123         output_dir_path = tempfile.mkdtemp()
124         used_tempdir = True
125         LOGGER.warn('Using a temporary directory "%s".' % output_dir_path)
126       else:
127         LOGGER.warn('The directory "%s" is not available.' % output_dir_path)
128         return None, used_tempdir
129   else:
130     LOGGER.info('Creating a new directory "%s".' % output_dir_path)
131     try:
132       os.mkdir(output_dir_path)
133     except OSError:
134       LOGGER.warn('A directory "%s" cannot be created.' % output_dir_path)
135       if use_tempdir:
136         output_dir_path = tempfile.mkdtemp()
137         used_tempdir = True
138         LOGGER.warn('Using a temporary directory "%s".' % output_dir_path)
139       else:
140         LOGGER.warn('The directory "%s" is not available.' % output_dir_path)
141         return None, used_tempdir
142
143   shutil.copyfile(maps_path, os.path.join(output_dir_path, 'maps'))
144
145   with open(maps_path, mode='r') as f:
146     maps = ProcMaps.load_file(f)
147
148   LOGGER.debug('Listing up symbols.')
149   files = {}
150   for entry in maps.iter(ProcMaps.executable):
151     LOGGER.debug('  %016x-%016x +%06x %s' % (
152         entry.begin, entry.end, entry.offset, entry.name))
153     binary_path = entry.name
154     for target_path, host_path in alternative_dirs.iteritems():
155       if entry.name.startswith(target_path):
156         binary_path = entry.name.replace(target_path, host_path, 1)
157     if not (ProcMaps.EXECUTABLE_PATTERN.match(binary_path) or
158             (os.path.isfile(binary_path) and os.access(binary_path, os.X_OK))):
159       continue
160     nm_filename = _dump_command_result(
161         'nm -n --format bsd %s | c++filt' % binary_path,
162         output_dir_path, os.path.basename(binary_path), '.nm')
163     if not nm_filename:
164       continue
165     readelf_e_filename = _dump_command_result(
166         'readelf -eW %s' % binary_path,
167         output_dir_path, os.path.basename(binary_path), '.readelf-e')
168     if not readelf_e_filename:
169       continue
170     readelf_debug_decodedline_file = None
171     if use_source_file_name:
172       readelf_debug_decodedline_file = _dump_command_result(
173           'readelf -wL %s | %s' % (binary_path, REDUCE_DEBUGLINE_PATH),
174           output_dir_path, os.path.basename(binary_path), '.readelf-wL')
175
176     files[entry.name] = {}
177     files[entry.name]['nm'] = {
178         'file': os.path.basename(nm_filename),
179         'format': 'bsd',
180         'mangled': False}
181     files[entry.name]['readelf-e'] = {
182         'file': os.path.basename(readelf_e_filename)}
183     if readelf_debug_decodedline_file:
184       files[entry.name]['readelf-debug-decodedline-file'] = {
185           'file': os.path.basename(readelf_debug_decodedline_file)}
186
187     files[entry.name]['size'] = os.stat(binary_path).st_size
188
189     with open(binary_path, 'rb') as entry_f:
190       md5 = hashlib.md5()
191       sha1 = hashlib.sha1()
192       chunk = entry_f.read(1024 * 1024)
193       while chunk:
194         md5.update(chunk)
195         sha1.update(chunk)
196         chunk = entry_f.read(1024 * 1024)
197       files[entry.name]['sha1'] = sha1.hexdigest()
198       files[entry.name]['md5'] = md5.hexdigest()
199
200   with open(os.path.join(output_dir_path, 'files.json'), 'w') as f:
201     json.dump(files, f, indent=2, sort_keys=True)
202
203   LOGGER.info('Collected symbol information at "%s".' % output_dir_path)
204   return output_dir_path, used_tempdir
205
206
207 def main():
208   if not sys.platform.startswith('linux'):
209     sys.stderr.write('This script work only on Linux.')
210     return 1
211
212   option_parser = optparse.OptionParser(
213       '%s /path/to/maps [/path/to/output_data_dir/]' % sys.argv[0])
214   option_parser.add_option('--alternative-dirs', dest='alternative_dirs',
215                            metavar='/path/on/target@/path/on/host[:...]',
216                            help='Read files in /path/on/host/ instead of '
217                            'files in /path/on/target/.')
218   option_parser.add_option('--verbose', dest='verbose', action='store_true',
219                            help='Enable verbose mode.')
220   options, args = option_parser.parse_args(sys.argv)
221   alternative_dirs_dict = {}
222   if options.alternative_dirs:
223     for alternative_dir_pair in options.alternative_dirs.split(':'):
224       target_path, host_path = alternative_dir_pair.split('@', 1)
225       alternative_dirs_dict[target_path] = host_path
226
227   LOGGER.setLevel(logging.DEBUG)
228   handler = logging.StreamHandler()
229   if options.verbose:
230     handler.setLevel(logging.DEBUG)
231   else:
232     handler.setLevel(logging.INFO)
233   formatter = logging.Formatter('%(message)s')
234   handler.setFormatter(formatter)
235   LOGGER.addHandler(handler)
236
237   if len(args) < 2:
238     option_parser.error('Argument error.')
239     return 1
240   if len(args) == 2:
241     result, _ = prepare_symbol_info(args[1],
242                                     alternative_dirs=alternative_dirs_dict)
243   else:
244     result, _ = prepare_symbol_info(args[1], args[2],
245                                     alternative_dirs=alternative_dirs_dict)
246
247   return not result
248
249
250 if __name__ == '__main__':
251   sys.exit(main())