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