roll libyuv to r1025 for mips n32 support, arm nacl port, psnr tool jpeg support.
[chromium-blink-merge.git] / tools / find_runtime_symbols / prepare_symbol_info.py
blob17d34deaa22b0fca032125ffec44c0e0fd12872d
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 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
17 BASE_PATH = os.path.dirname(os.path.abspath(__file__))
18 REDUCE_DEBUGLINE_PATH = os.path.join(BASE_PATH, 'reduce_debugline.py')
19 _TOOLS_LINUX_PATH = os.path.join(BASE_PATH, os.pardir, 'linux')
20 sys.path.insert(0, _TOOLS_LINUX_PATH)
23 from procfs import ProcMaps # pylint: disable=F0401
26 LOGGER = logging.getLogger('prepare_symbol_info')
29 def _dump_command_result(command, output_dir_path, basename, suffix):
30 handle_out, filename_out = tempfile.mkstemp(
31 suffix=suffix, prefix=basename + '.', dir=output_dir_path)
32 handle_err, filename_err = tempfile.mkstemp(
33 suffix=suffix + '.err', prefix=basename + '.', dir=output_dir_path)
34 error = False
35 try:
36 subprocess.check_call(
37 command, stdout=handle_out, stderr=handle_err, shell=True)
38 except (OSError, subprocess.CalledProcessError):
39 error = True
40 finally:
41 os.close(handle_err)
42 os.close(handle_out)
44 if os.path.exists(filename_err):
45 if LOGGER.getEffectiveLevel() <= logging.DEBUG:
46 with open(filename_err, 'r') as f:
47 for line in f:
48 LOGGER.debug(line.rstrip())
49 os.remove(filename_err)
51 if os.path.exists(filename_out) and (
52 os.path.getsize(filename_out) == 0 or error):
53 os.remove(filename_out)
54 return None
56 if not os.path.exists(filename_out):
57 return None
59 return filename_out
62 def prepare_symbol_info(maps_path,
63 output_dir_path=None,
64 alternative_dirs=None,
65 use_tempdir=False,
66 use_source_file_name=False):
67 """Prepares (collects) symbol information files for find_runtime_symbols.
69 1) If |output_dir_path| is specified, it tries collecting symbol information
70 files in the given directory |output_dir_path|.
71 1-a) If |output_dir_path| doesn't exist, create the directory and use it.
72 1-b) If |output_dir_path| is an empty directory, use it.
73 1-c) If |output_dir_path| is a directory which has 'files.json', assumes that
74 files are already collected and just ignores it.
75 1-d) Otherwise, depends on |use_tempdir|.
77 2) If |output_dir_path| is not specified, it tries to create a new directory
78 depending on 'maps_path'.
80 If it cannot create a new directory, creates a temporary directory depending
81 on |use_tempdir|. If |use_tempdir| is False, returns None.
83 Args:
84 maps_path: A path to a file which contains '/proc/<pid>/maps'.
85 alternative_dirs: A mapping from a directory '/path/on/target' where the
86 target process runs to a directory '/path/on/host' where the script
87 reads the binary. Considered to be used for Android binaries.
88 output_dir_path: A path to a directory where files are prepared.
89 use_tempdir: If True, it creates a temporary directory when it cannot
90 create a new directory.
91 use_source_file_name: If True, it adds reduced result of 'readelf -wL'
92 to find source file names.
94 Returns:
95 A pair of a path to the prepared directory and a boolean representing
96 if it created a temporary directory or not.
97 """
98 alternative_dirs = alternative_dirs or {}
99 if not output_dir_path:
100 matched = re.match('^(.*)\.maps$', os.path.basename(maps_path))
101 if matched:
102 output_dir_path = matched.group(1) + '.pre'
103 if not output_dir_path:
104 matched = re.match('^/proc/(.*)/maps$', os.path.realpath(maps_path))
105 if matched:
106 output_dir_path = matched.group(1) + '.pre'
107 if not output_dir_path:
108 output_dir_path = os.path.basename(maps_path) + '.pre'
109 # TODO(dmikurube): Find another candidate for output_dir_path.
111 used_tempdir = False
112 LOGGER.info('Data for profiling will be collected in "%s".' % output_dir_path)
113 if os.path.exists(output_dir_path):
114 if os.path.isdir(output_dir_path) and not os.listdir(output_dir_path):
115 LOGGER.warn('Using an empty existing directory "%s".' % output_dir_path)
116 else:
117 LOGGER.warn('A file or a directory exists at "%s".' % output_dir_path)
118 if os.path.exists(os.path.join(output_dir_path, 'files.json')):
119 LOGGER.warn('Using the existing directory "%s".' % output_dir_path)
120 return output_dir_path, used_tempdir
121 else:
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
143 shutil.copyfile(maps_path, os.path.join(output_dir_path, 'maps'))
145 with open(maps_path, mode='r') as f:
146 maps = ProcMaps.load_file(f)
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')
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)}
187 files[entry.name]['size'] = os.stat(binary_path).st_size
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()
200 with open(os.path.join(output_dir_path, 'files.json'), 'w') as f:
201 json.dump(files, f, indent=2, sort_keys=True)
203 LOGGER.info('Collected symbol information at "%s".' % output_dir_path)
204 return output_dir_path, used_tempdir
207 def main():
208 if not sys.platform.startswith('linux'):
209 sys.stderr.write('This script work only on Linux.')
210 return 1
212 LOGGER.setLevel(logging.DEBUG)
213 handler = logging.StreamHandler()
214 handler.setLevel(logging.INFO)
215 formatter = logging.Formatter('%(message)s')
216 handler.setFormatter(formatter)
217 LOGGER.addHandler(handler)
219 # TODO(dmikurube): Specify |alternative_dirs| from command line.
220 if len(sys.argv) < 2:
221 sys.stderr.write("""Usage:
222 %s /path/to/maps [/path/to/output_data_dir/]
223 """ % sys.argv[0])
224 return 1
225 elif len(sys.argv) == 2:
226 result, _ = prepare_symbol_info(sys.argv[1])
227 else:
228 result, _ = prepare_symbol_info(sys.argv[1], sys.argv[2])
230 return not result
233 if __name__ == '__main__':
234 sys.exit(main())