Battery Status API: add UMA logging for Linux.
[chromium-blink-merge.git] / tools / find_runtime_symbols / prepare_symbol_info.py
blobbefe314ab0718f1f23c061cedfc375c495606cd0
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 optparse
10 import os
11 import re
12 import shutil
13 import subprocess
14 import sys
15 import tempfile
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)
24 from procfs import ProcMaps # pylint: disable=F0401
27 LOGGER = logging.getLogger('prepare_symbol_info')
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)
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)
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
57 if not os.path.exists(filename_out):
58 return None
60 return filename_out
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.
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|.
78 2) If |output_dir_path| is not specified, it tries to create a new directory
79 depending on 'maps_path'.
81 If it cannot create a new directory, creates a temporary directory depending
82 on |use_tempdir|. If |use_tempdir| is False, returns None.
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.
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.
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 else:
123 if use_tempdir:
124 output_dir_path = tempfile.mkdtemp()
125 used_tempdir = True
126 LOGGER.warn('Using a temporary directory "%s".' % output_dir_path)
127 else:
128 LOGGER.warn('The directory "%s" is not available.' % output_dir_path)
129 return None, used_tempdir
130 else:
131 LOGGER.info('Creating a new directory "%s".' % output_dir_path)
132 try:
133 os.mkdir(output_dir_path)
134 except OSError:
135 LOGGER.warn('A directory "%s" cannot be created.' % output_dir_path)
136 if use_tempdir:
137 output_dir_path = tempfile.mkdtemp()
138 used_tempdir = True
139 LOGGER.warn('Using a temporary directory "%s".' % output_dir_path)
140 else:
141 LOGGER.warn('The directory "%s" is not available.' % output_dir_path)
142 return None, used_tempdir
144 shutil.copyfile(maps_path, os.path.join(output_dir_path, 'maps'))
146 with open(maps_path, mode='r') as f:
147 maps = ProcMaps.load_file(f)
149 LOGGER.debug('Listing up symbols.')
150 files = {}
151 for entry in maps.iter(ProcMaps.executable):
152 LOGGER.debug(' %016x-%016x +%06x %s' % (
153 entry.begin, entry.end, entry.offset, entry.name))
154 binary_path = entry.name
155 for target_path, host_path in alternative_dirs.iteritems():
156 if entry.name.startswith(target_path):
157 binary_path = entry.name.replace(target_path, host_path, 1)
158 if not (ProcMaps.EXECUTABLE_PATTERN.match(binary_path) or
159 (os.path.isfile(binary_path) and os.access(binary_path, os.X_OK))):
160 continue
161 nm_filename = _dump_command_result(
162 'nm -n --format bsd %s | c++filt' % binary_path,
163 output_dir_path, os.path.basename(binary_path), '.nm')
164 if not nm_filename:
165 continue
166 readelf_e_filename = _dump_command_result(
167 'readelf -eW %s' % binary_path,
168 output_dir_path, os.path.basename(binary_path), '.readelf-e')
169 if not readelf_e_filename:
170 continue
171 readelf_debug_decodedline_file = None
172 if use_source_file_name:
173 readelf_debug_decodedline_file = _dump_command_result(
174 'readelf -wL %s | %s' % (binary_path, REDUCE_DEBUGLINE_PATH),
175 output_dir_path, os.path.basename(binary_path), '.readelf-wL')
177 files[entry.name] = {}
178 files[entry.name]['nm'] = {
179 'file': os.path.basename(nm_filename),
180 'format': 'bsd',
181 'mangled': False}
182 files[entry.name]['readelf-e'] = {
183 'file': os.path.basename(readelf_e_filename)}
184 if readelf_debug_decodedline_file:
185 files[entry.name]['readelf-debug-decodedline-file'] = {
186 'file': os.path.basename(readelf_debug_decodedline_file)}
188 files[entry.name]['size'] = os.stat(binary_path).st_size
190 with open(binary_path, 'rb') as entry_f:
191 md5 = hashlib.md5()
192 sha1 = hashlib.sha1()
193 chunk = entry_f.read(1024 * 1024)
194 while chunk:
195 md5.update(chunk)
196 sha1.update(chunk)
197 chunk = entry_f.read(1024 * 1024)
198 files[entry.name]['sha1'] = sha1.hexdigest()
199 files[entry.name]['md5'] = md5.hexdigest()
201 with open(os.path.join(output_dir_path, 'files.json'), 'w') as f:
202 json.dump(files, f, indent=2, sort_keys=True)
204 LOGGER.info('Collected symbol information at "%s".' % output_dir_path)
205 return output_dir_path, used_tempdir
208 def main():
209 if not sys.platform.startswith('linux'):
210 sys.stderr.write('This script work only on Linux.')
211 return 1
213 option_parser = optparse.OptionParser(
214 '%s /path/to/maps [/path/to/output_data_dir/]' % sys.argv[0])
215 option_parser.add_option('--alternative-dirs', dest='alternative_dirs',
216 metavar='/path/on/target@/path/on/host[:...]',
217 help='Read files in /path/on/host/ instead of '
218 'files in /path/on/target/.')
219 option_parser.add_option('--verbose', dest='verbose', action='store_true',
220 help='Enable verbose mode.')
221 options, args = option_parser.parse_args(sys.argv)
222 alternative_dirs_dict = {}
223 if options.alternative_dirs:
224 for alternative_dir_pair in options.alternative_dirs.split(':'):
225 target_path, host_path = alternative_dir_pair.split('@', 1)
226 alternative_dirs_dict[target_path] = host_path
228 LOGGER.setLevel(logging.DEBUG)
229 handler = logging.StreamHandler()
230 if options.verbose:
231 handler.setLevel(logging.DEBUG)
232 else:
233 handler.setLevel(logging.INFO)
234 formatter = logging.Formatter('%(message)s')
235 handler.setFormatter(formatter)
236 LOGGER.addHandler(handler)
238 if len(args) < 2:
239 option_parser.error('Argument error.')
240 return 1
241 elif len(args) == 2:
242 result, _ = prepare_symbol_info(args[1],
243 alternative_dirs=alternative_dirs_dict)
244 else:
245 result, _ = prepare_symbol_info(args[1], args[2],
246 alternative_dirs=alternative_dirs_dict)
248 return not result
251 if __name__ == '__main__':
252 sys.exit(main())