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.
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
)
37 subprocess
.check_call(
38 command
, stdout
=handle_out
, stderr
=handle_err
, shell
=True)
39 except (OSError, subprocess
.CalledProcessError
):
45 if os
.path
.exists(filename_err
):
46 if LOGGER
.getEffectiveLevel() <= logging
.DEBUG
:
47 with
open(filename_err
, 'r') as 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
)
57 if not os
.path
.exists(filename_out
):
63 def prepare_symbol_info(maps_path
,
65 alternative_dirs
=None,
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.
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.
96 A pair of a path to the prepared directory and a boolean representing
97 if it created a temporary directory or not.
99 alternative_dirs
= alternative_dirs
or {}
100 if not output_dir_path
:
101 matched
= re
.match('^(.*)\.maps$', os
.path
.basename(maps_path
))
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
))
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.
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
)
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
124 output_dir_path
= tempfile
.mkdtemp()
126 LOGGER
.warn('Using a temporary directory "%s".' % output_dir_path
)
128 LOGGER
.warn('The directory "%s" is not available.' % output_dir_path
)
129 return None, used_tempdir
131 LOGGER
.info('Creating a new directory "%s".' % output_dir_path
)
133 os
.mkdir(output_dir_path
)
135 LOGGER
.warn('A directory "%s" cannot be created.' % output_dir_path
)
137 output_dir_path
= tempfile
.mkdtemp()
139 LOGGER
.warn('Using a temporary directory "%s".' % output_dir_path
)
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.')
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
))):
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')
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
:
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
),
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
:
192 sha1
= hashlib
.sha1()
193 chunk
= entry_f
.read(1024 * 1024)
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
209 if not sys
.platform
.startswith('linux'):
210 sys
.stderr
.write('This script work only on Linux.')
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()
231 handler
.setLevel(logging
.DEBUG
)
233 handler
.setLevel(logging
.INFO
)
234 formatter
= logging
.Formatter('%(message)s')
235 handler
.setFormatter(formatter
)
236 LOGGER
.addHandler(handler
)
239 option_parser
.error('Argument error.')
242 result
, _
= prepare_symbol_info(args
[1],
243 alternative_dirs
=alternative_dirs_dict
)
245 result
, _
= prepare_symbol_info(args
[1], args
[2],
246 alternative_dirs
=alternative_dirs_dict
)
251 if __name__
== '__main__':