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 import decorators
15 from telemetry.core import platform as telemetry_platform
16 from telemetry.core import util
17 from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
18 from telemetry.util import support_binaries
27 _TEXT_SECTION = '.text'
30 def _ElfMachineId(elf_file):
31 headers = subprocess.check_output(['readelf', '-h', elf_file])
32 return re.match(r'.*Machine:\s+(\w+)', headers, re.DOTALL).group(1)
35 def _ElfSectionAsString(elf_file, section):
36 return subprocess.check_output(['readelf', '-p', section, elf_file])
39 def _ElfSectionMd5Sum(elf_file, section):
40 result = subprocess.check_output(
41 'readelf -p%s "%s" | md5sum' % (section, elf_file), shell=True)
42 return result.split(' ', 1)[0]
45 def _FindMatchingUnstrippedLibraryOnHost(device, lib):
46 lib_base = os.path.basename(lib)
48 device_md5 = device.RunShellCommand('md5 "%s"' % lib, as_root=True)[0]
49 device_md5 = device_md5.split(' ', 1)[0]
51 def FindMatchingStrippedLibrary(out_path):
52 # First find a matching stripped library on the host. This avoids the need
53 # to pull the stripped library from the device, which can take tens of
55 host_lib_pattern = os.path.join(out_path, '*_apk', 'libs', '*', lib_base)
56 for stripped_host_lib in glob.glob(host_lib_pattern):
57 with open(stripped_host_lib) as f:
58 host_md5 = hashlib.md5(f.read()).hexdigest()
59 if host_md5 == device_md5:
60 return stripped_host_lib
62 for build_dir, build_type in util.GetBuildDirectories():
63 out_path = os.path.join(build_dir, build_type)
64 stripped_host_lib = FindMatchingStrippedLibrary(out_path)
70 # The corresponding unstripped library will be under out/Release/lib.
71 unstripped_host_lib = os.path.join(out_path, 'lib', lib_base)
73 # Make sure the unstripped library matches the stripped one. We do this
74 # by comparing the hashes of text sections in both libraries. This isn't an
75 # exact guarantee, but should still give reasonable confidence that the
76 # libraries are compatible.
77 # TODO(skyostil): Check .note.gnu.build-id instead once we're using
79 # pylint: disable=W0631
80 if (_ElfSectionMd5Sum(unstripped_host_lib, _TEXT_SECTION) !=
81 _ElfSectionMd5Sum(stripped_host_lib, _TEXT_SECTION)):
83 return unstripped_host_lib
87 def GetPerfhostName():
88 return 'perfhost_' + telemetry_platform.GetHostPlatform().GetOSVersionName()
91 # Ignored directories for libraries that aren't useful for symbolization.
92 _IGNORED_LIB_PATHS = [
98 def GetRequiredLibrariesForPerfProfile(profile_file):
99 """Returns the set of libraries necessary to symbolize a given perf profile.
102 profile_file: Path to perf profile to analyse.
105 A set of required library file names.
107 with open(os.devnull, 'w') as dev_null:
108 perfhost_path = support_binaries.FindPath(
109 GetPerfhostName(), 'x86_64', 'linux')
110 perf = subprocess.Popen([perfhost_path, 'script', '-i', profile_file],
111 stdout=dev_null, stderr=subprocess.PIPE)
112 _, output = perf.communicate()
113 missing_lib_re = re.compile(
114 r'^Failed to open (.*), continuing without symbols')
116 for line in output.split('\n'):
117 lib = missing_lib_re.match(line)
120 path = os.path.dirname(lib)
121 if (any(path.startswith(ignored_path)
122 for ignored_path in _IGNORED_LIB_PATHS)
123 or path == '/' or not path):
129 def GetRequiredLibrariesForVTuneProfile(profile_file):
130 """Returns the set of libraries necessary to symbolize a given VTune profile.
133 profile_file: Path to VTune profile to analyse.
136 A set of required library file names.
138 db_file = os.path.join(profile_file, 'sqlite-db', 'dicer.db')
139 conn = sqlite3.connect(db_file)
142 # The 'dd_module_file' table lists all libraries on the device. Only the
143 # ones with 'bin_located_path' are needed for the profile.
144 query = 'SELECT bin_path, bin_located_path FROM dd_module_file'
145 return set(row[0] for row in conn.cursor().execute(query) if row[1])
150 def CreateSymFs(device, symfs_dir, libraries, use_symlinks=True):
151 """Creates a symfs directory to be used for symbolizing profiles.
153 Prepares a set of files ("symfs") to be used with profilers such as perf for
154 converting binary addresses into human readable function names.
157 device: DeviceUtils instance identifying the target device.
158 symfs_dir: Path where the symfs should be created.
159 libraries: Set of library file names that should be included in the symfs.
160 use_symlinks: If True, link instead of copy unstripped libraries into the
161 symfs. This will speed up the operation, but the resulting symfs will no
162 longer be valid if the linked files are modified, e.g., by rebuilding.
165 The absolute path to the kernel symbols within the created symfs.
167 logging.info('Building symfs into %s.' % symfs_dir)
169 mismatching_files = {}
170 for lib in libraries:
171 device_dir = os.path.dirname(lib)
172 output_dir = os.path.join(symfs_dir, device_dir[1:])
173 if not os.path.exists(output_dir):
174 os.makedirs(output_dir)
175 output_lib = os.path.join(output_dir, os.path.basename(lib))
177 if lib.startswith('/data/app/'):
178 # If this is our own library instead of a system one, look for a matching
179 # unstripped library under the out directory.
180 unstripped_host_lib = _FindMatchingUnstrippedLibraryOnHost(device, lib)
181 if not unstripped_host_lib:
182 logging.warning('Could not find symbols for %s.' % lib)
183 logging.warning('Is the correct output directory selected '
184 '(CHROMIUM_OUT_DIR)? Did you install the APK after '
188 if os.path.lexists(output_lib):
189 os.remove(output_lib)
190 os.symlink(os.path.abspath(unstripped_host_lib), output_lib)
191 # Copy the unstripped library only if it has been changed to avoid the
192 # delay. Add one second to the modification time to guard against file
193 # systems with poor timestamp resolution.
194 elif not os.path.exists(output_lib) or \
195 (os.stat(unstripped_host_lib).st_mtime >
196 os.stat(output_lib).st_mtime + 1):
197 logging.info('Copying %s to %s' % (unstripped_host_lib, output_lib))
198 shutil.copy2(unstripped_host_lib, output_lib)
200 # Otherwise save a copy of the stripped system library under the symfs so
201 # the profiler can at least use the public symbols of that library. To
202 # speed things up, only pull files that don't match copies we already
204 if not device_dir in mismatching_files:
205 changed_files = device.old_interface.GetFilesChanged(output_dir,
207 mismatching_files[device_dir] = [
208 device_path for _, device_path in changed_files]
210 if not os.path.exists(output_lib) or lib in mismatching_files[device_dir]:
211 logging.info('Pulling %s to %s' % (lib, output_lib))
212 device.PullFile(lib, output_lib)
214 # Also pull a copy of the kernel symbols.
215 output_kallsyms = os.path.join(symfs_dir, 'kallsyms')
216 if not os.path.exists(output_kallsyms):
217 device.PullFile('/proc/kallsyms', output_kallsyms)
218 return output_kallsyms
221 def PrepareDeviceForPerf(device):
222 """Set up a device for running perf.
225 device: DeviceUtils instance identifying the target device.
228 The path to the installed perf binary on the device.
230 android_prebuilt_profiler_helper.InstallOnDevice(device, 'perf')
231 # Make sure kernel pointers are not hidden.
232 device.WriteFile('/proc/sys/kernel/kptr_restrict', '0', as_root=True)
233 return android_prebuilt_profiler_helper.GetDevicePath('perf')
236 def GetToolchainBinaryPath(library_file, binary_name):
237 """Return the path to an Android toolchain binary on the host.
240 library_file: ELF library which is used to identify the used ABI,
241 architecture and toolchain.
242 binary_name: Binary to search for, e.g., 'objdump'
244 Full path to binary or None if the binary was not found.
246 # Mapping from ELF machine identifiers to GNU toolchain names.
247 toolchain_configs = {
248 'x86': 'i686-linux-android',
249 'MIPS': 'mipsel-linux-android',
250 'ARM': 'arm-linux-androideabi',
251 'x86-64': 'x86_64-linux-android',
252 'AArch64': 'aarch64-linux-android',
254 toolchain_config = toolchain_configs[_ElfMachineId(library_file)]
255 host_os = platform.uname()[0].lower()
256 host_machine = platform.uname()[4]
258 elf_comment = _ElfSectionAsString(library_file, '.comment')
259 toolchain_version = re.match(r'.*GCC: \(GNU\) ([\w.]+)',
260 elf_comment, re.DOTALL)
261 if not toolchain_version:
263 toolchain_version = toolchain_version.group(1)
265 path = os.path.join(util.GetChromiumSrcDir(), 'third_party', 'android_tools',
267 '%s-%s' % (toolchain_config, toolchain_version),
268 'prebuilt', '%s-%s' % (host_os, host_machine), 'bin',
269 '%s-%s' % (toolchain_config, binary_name))
270 path = os.path.abspath(path)
271 return path if os.path.exists(path) else None