1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
14 from telemetry.core import util
15 from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
16 from telemetry.util import support_binaries
25 _TEXT_SECTION = '.text'
28 def _ElfMachineId(elf_file):
29 headers = subprocess.check_output(['readelf', '-h', elf_file])
30 return re.match(r'.*Machine:\s+(\w+)', headers, re.DOTALL).group(1)
33 def _ElfSectionAsString(elf_file, section):
34 return subprocess.check_output(['readelf', '-p', section, elf_file])
37 def _ElfSectionMd5Sum(elf_file, section):
38 result = subprocess.check_output(
39 'readelf -p%s "%s" | md5sum' % (section, elf_file), shell=True)
40 return result.split(' ', 1)[0]
43 def _FindMatchingUnstrippedLibraryOnHost(device, lib):
44 lib_base = os.path.basename(lib)
46 device_md5 = device.RunShellCommand('md5 "%s"' % lib, as_root=True)[0]
47 device_md5 = device_md5.split(' ', 1)[0]
49 def FindMatchingStrippedLibrary(out_path):
50 # First find a matching stripped library on the host. This avoids the need
51 # to pull the stripped library from the device, which can take tens of
53 host_lib_pattern = os.path.join(out_path, '*_apk', 'libs', '*', lib_base)
54 for stripped_host_lib in glob.glob(host_lib_pattern):
55 with open(stripped_host_lib) as f:
56 host_md5 = hashlib.md5(f.read()).hexdigest()
57 if host_md5 == device_md5:
58 return stripped_host_lib
60 for build_dir, build_type in util.GetBuildDirectories():
61 out_path = os.path.join(build_dir, build_type)
62 stripped_host_lib = FindMatchingStrippedLibrary(out_path)
68 # The corresponding unstripped library will be under out/Release/lib.
69 unstripped_host_lib = os.path.join(out_path, 'lib', lib_base)
71 # Make sure the unstripped library matches the stripped one. We do this
72 # by comparing the hashes of text sections in both libraries. This isn't an
73 # exact guarantee, but should still give reasonable confidence that the
74 # libraries are compatible.
75 # TODO(skyostil): Check .note.gnu.build-id instead once we're using
77 # pylint: disable=W0631
78 if (_ElfSectionMd5Sum(unstripped_host_lib, _TEXT_SECTION) !=
79 _ElfSectionMd5Sum(stripped_host_lib, _TEXT_SECTION)):
81 return unstripped_host_lib
84 # Ignored directories for libraries that aren't useful for symbolization.
85 _IGNORED_LIB_PATHS = [
91 def GetRequiredLibrariesForPerfProfile(profile_file):
92 """Returns the set of libraries necessary to symbolize a given perf profile.
95 profile_file: Path to perf profile to analyse.
98 A set of required library file names.
100 with open(os.devnull, 'w') as dev_null:
101 perfhost_path = support_binaries.FindPath('perfhost', 'linux')
102 perf = subprocess.Popen([perfhost_path, 'script', '-i', profile_file],
103 stdout=dev_null, stderr=subprocess.PIPE)
104 _, output = perf.communicate()
105 missing_lib_re = re.compile(
106 r'^Failed to open (.*), continuing without symbols')
108 for line in output.split('\n'):
109 lib = missing_lib_re.match(line)
112 path = os.path.dirname(lib)
113 if any(path.startswith(ignored_path)
114 for ignored_path in _IGNORED_LIB_PATHS) or path == '/':
120 def GetRequiredLibrariesForVTuneProfile(profile_file):
121 """Returns the set of libraries necessary to symbolize a given VTune profile.
124 profile_file: Path to VTune profile to analyse.
127 A set of required library file names.
129 db_file = os.path.join(profile_file, 'sqlite-db', 'dicer.db')
130 conn = sqlite3.connect(db_file)
133 # The 'dd_module_file' table lists all libraries on the device. Only the
134 # ones with 'bin_located_path' are needed for the profile.
135 query = 'SELECT bin_path, bin_located_path FROM dd_module_file'
136 return set(row[0] for row in conn.cursor().execute(query) if row[1])
141 def CreateSymFs(device, symfs_dir, libraries, use_symlinks=True):
142 """Creates a symfs directory to be used for symbolizing profiles.
144 Prepares a set of files ("symfs") to be used with profilers such as perf for
145 converting binary addresses into human readable function names.
148 device: DeviceUtils instance identifying the target device.
149 symfs_dir: Path where the symfs should be created.
150 libraries: Set of library file names that should be included in the symfs.
151 use_symlinks: If True, link instead of copy unstripped libraries into the
152 symfs. This will speed up the operation, but the resulting symfs will no
153 longer be valid if the linked files are modified, e.g., by rebuilding.
156 The absolute path to the kernel symbols within the created symfs.
158 logging.info('Building symfs into %s.' % symfs_dir)
160 mismatching_files = {}
161 for lib in libraries:
162 device_dir = os.path.dirname(lib)
163 output_dir = os.path.join(symfs_dir, device_dir[1:])
164 if not os.path.exists(output_dir):
165 os.makedirs(output_dir)
166 output_lib = os.path.join(output_dir, os.path.basename(lib))
168 if lib.startswith('/data/app-lib/'):
169 # If this is our own library instead of a system one, look for a matching
170 # unstripped library under the out directory.
171 unstripped_host_lib = _FindMatchingUnstrippedLibraryOnHost(device, lib)
172 if not unstripped_host_lib:
173 logging.warning('Could not find symbols for %s.' % lib)
174 logging.warning('Is the correct output directory selected '
175 '(CHROMIUM_OUT_DIR)? Did you install the APK after '
179 if os.path.lexists(output_lib):
180 os.remove(output_lib)
181 os.symlink(os.path.abspath(unstripped_host_lib), output_lib)
182 # Copy the unstripped library only if it has been changed to avoid the
183 # delay. Add one second to the modification time to guard against file
184 # systems with poor timestamp resolution.
185 elif not os.path.exists(output_lib) or \
186 (os.stat(unstripped_host_lib).st_mtime >
187 os.stat(output_lib).st_mtime + 1):
188 logging.info('Copying %s to %s' % (unstripped_host_lib, output_lib))
189 shutil.copy2(unstripped_host_lib, output_lib)
191 # Otherwise save a copy of the stripped system library under the symfs so
192 # the profiler can at least use the public symbols of that library. To
193 # speed things up, only pull files that don't match copies we already
195 if not device_dir in mismatching_files:
196 changed_files = device.old_interface.GetFilesChanged(output_dir,
198 mismatching_files[device_dir] = [
199 device_path for _, device_path in changed_files]
201 if not os.path.exists(output_lib) or lib in mismatching_files[device_dir]:
202 logging.info('Pulling %s to %s' % (lib, output_lib))
203 device.PullFile(lib, output_lib)
205 # Also pull a copy of the kernel symbols.
206 output_kallsyms = os.path.join(symfs_dir, 'kallsyms')
207 if not os.path.exists(output_kallsyms):
208 device.PullFile('/proc/kallsyms', output_kallsyms)
209 return output_kallsyms
212 def PrepareDeviceForPerf(device):
213 """Set up a device for running perf.
216 device: DeviceUtils instance identifying the target device.
219 The path to the installed perf binary on the device.
221 android_prebuilt_profiler_helper.InstallOnDevice(device, 'perf')
222 # Make sure kernel pointers are not hidden.
223 device.WriteFile('/proc/sys/kernel/kptr_restrict', '0', as_root=True)
224 return android_prebuilt_profiler_helper.GetDevicePath('perf')
227 def GetToolchainBinaryPath(library_file, binary_name):
228 """Return the path to an Android toolchain binary on the host.
231 library_file: ELF library which is used to identify the used ABI,
232 architecture and toolchain.
233 binary_name: Binary to search for, e.g., 'objdump'
235 Full path to binary or None if the binary was not found.
237 # Mapping from ELF machine identifiers to GNU toolchain names.
238 toolchain_configs = {
239 'x86': 'i686-linux-android',
240 'MIPS': 'mipsel-linux-android',
241 'ARM': 'arm-linux-androideabi',
242 'x86-64': 'x86_64-linux-android',
243 'AArch64': 'aarch64-linux-android',
245 toolchain_config = toolchain_configs[_ElfMachineId(library_file)]
246 host_os = platform.uname()[0].lower()
247 host_machine = platform.uname()[4]
249 elf_comment = _ElfSectionAsString(library_file, '.comment')
250 toolchain_version = re.match(r'.*GCC: \(GNU\) ([\w.]+)',
251 elf_comment, re.DOTALL)
252 if not toolchain_version:
254 toolchain_version = toolchain_version.group(1)
256 path = os.path.join(util.GetChromiumSrcDir(), 'third_party', 'android_tools',
258 '%s-%s' % (toolchain_config, toolchain_version),
259 'prebuilt', '%s-%s' % (host_os, host_machine), 'bin',
260 '%s-%s' % (toolchain_config, binary_name))
261 path = os.path.abspath(path)
262 return path if os.path.exists(path) else None