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