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.
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
)
30 subprocess
.check_call(
31 command
, stdout
=handle_out
, stderr
=handle_err
, shell
=True)
32 except (OSError, subprocess
.CalledProcessError
):
38 if os
.path
.exists(filename_err
):
39 if LOGGER
.getEffectiveLevel() <= logging
.DEBUG
:
40 with
open(filename_err
, 'r') as 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
)
50 if not os
.path
.exists(filename_out
):
56 def prepare_symbol_info(maps_path
,
58 fake_directories
=None,
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.
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.
89 A pair of a path to the prepared directory and a boolean representing
90 if it created a temporary directory or not.
92 fake_directories
= fake_directories
or {}
93 if not output_dir_path
:
94 matched
= re
.match('^(.*)\.maps$', os
.path
.basename(maps_path
))
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
))
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.
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
)
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
117 output_dir_path
= tempfile
.mkdtemp()
119 LOGGER
.warn('Using a temporary directory "%s".' % output_dir_path
)
121 LOGGER
.warn('The directory "%s" is not available.' % output_dir_path
)
122 return None, used_tempdir
124 LOGGER
.info('Creating a new directory "%s".' % output_dir_path
)
126 os
.mkdir(output_dir_path
)
128 LOGGER
.warn('A directory "%s" cannot be created.' % output_dir_path
)
130 output_dir_path
= tempfile
.mkdtemp()
132 LOGGER
.warn('Using a temporary directory "%s".' % output_dir_path
)
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.')
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')
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
:
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
),
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
186 if not sys
.platform
.startswith('linux'):
187 sys
.stderr
.write('This script work only on Linux.')
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/]
203 elif len(sys
.argv
) == 2:
204 result
, _
= prepare_symbol_info(sys
.argv
[1])
206 result
, _
= prepare_symbol_info(sys
.argv
[1], sys
.argv
[2])
211 if __name__
== '__main__':