diff options
Diffstat (limited to 'simpleperf/scripts/simpleperf_utils.py')
-rw-r--r-- | simpleperf/scripts/simpleperf_utils.py | 139 |
1 files changed, 95 insertions, 44 deletions
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py index 580db888..156d0e83 100644 --- a/simpleperf/scripts/simpleperf_utils.py +++ b/simpleperf/scripts/simpleperf_utils.py @@ -18,6 +18,7 @@ """utils.py: export utility functions. """ +from __future__ import annotations import argparse import logging import os @@ -28,6 +29,7 @@ import shutil import subprocess import sys import time +from typing import Dict, List, Optional, Union def get_script_dir(): @@ -315,6 +317,12 @@ class AdbHelper(object): self.enable_switch_to_root = enable_switch_to_root self.serial_number = None + def is_device_available(self) -> bool: + result, _ = self.run_and_return_output( + ['shell', 'whoami'], + log_output=False, log_stderr=False) + return result == True + def run(self, adb_args): return self.run_and_return_output(adb_args)[0] @@ -397,7 +405,7 @@ class AdbHelper(object): log_fatal('unsupported architecture: %s' % output.strip()) return '' - def get_android_version(self): + def get_android_version(self) -> int: """ Get Android version on device, like 7 is for Android N, 8 is for Android O.""" build_version = self.get_property('ro.build.version.release') android_version = 0 @@ -446,22 +454,53 @@ def open_report_in_browser(report_path): webbrowser.open_new_tab(report_path) -def is_elf_file(path): - if os.path.isfile(path): - with open(path, 'rb') as fh: - return fh.read(4) == b'\x7fELF' - return False - +class BinaryFinder: + def __init__(self, binary_cache_dir: Optional[Union[Path, str]], readelf: ReadElf): + if isinstance(binary_cache_dir, str): + binary_cache_dir = Path(binary_cache_dir) + self.binary_cache_dir = binary_cache_dir + self.readelf = readelf + self.build_id_map = self._load_build_id_map() + + def _load_build_id_map(self) -> Dict[str, Path]: + build_id_map: Dict[str, Path] = {} + if self.binary_cache_dir: + build_id_list_file = self.binary_cache_dir / 'build_id_list' + if build_id_list_file.is_file(): + with open(self.binary_cache_dir / 'build_id_list', 'rb') as fh: + for line in fh.readlines(): + # lines are in format "<build_id>=<path_in_binary_cache>". + items = bytes_to_str(line).strip().split('=') + if len(items) == 2: + build_id_map[items[0]] = self.binary_cache_dir / items[1] + return build_id_map + + def find_binary(self, dso_path_in_record_file: str, + expected_build_id: Optional[str]) -> Optional[Path]: + """ If expected_build_id is None, don't check build id. + Otherwise, the build id of the found binary should match the expected one.""" + # Find binary from build id map. + if expected_build_id: + path = self.build_id_map.get(expected_build_id) + if path and self._check_path(path, expected_build_id): + return path + # Find binary by path in binary cache. + if self.binary_cache_dir: + path = self.binary_cache_dir / dso_path_in_record_file[1:] + if self._check_path(path, expected_build_id): + return path + # Find binary by its absolute path. + path = Path(dso_path_in_record_file) + if self._check_path(path, expected_build_id): + return path + return None -def find_real_dso_path(dso_path_in_record_file, binary_cache_path): - """ Given the path of a shared library in perf.data, find its real path in the file system. """ - if binary_cache_path: - tmp_path = os.path.join(binary_cache_path, dso_path_in_record_file[1:]) - if is_elf_file(tmp_path): - return tmp_path - if is_elf_file(dso_path_in_record_file): - return dso_path_in_record_file - return None + def _check_path(self, path: Path, expected_build_id: Optional[str]) -> bool: + if not self.readelf.is_elf_file(path): + return False + if expected_build_id is not None: + return self.readelf.get_build_id(path) == expected_build_id + return True class Addr2Nearestline(object): @@ -496,7 +535,8 @@ class Addr2Nearestline(object): addrs: a map from address to Addr object in this dso. """ - def __init__(self): + def __init__(self, build_id: str): + self.build_id = build_id self.addrs = {} class Addr(object): @@ -510,13 +550,15 @@ class Addr2Nearestline(object): self.func_addr = func_addr self.source_lines = None - def __init__(self, ndk_path, binary_cache_path, with_function_name): + def __init__( + self, ndk_path: Optional[str], + binary_finder: BinaryFinder, with_function_name: bool): self.symbolizer_path = find_tool_path('llvm-symbolizer', ndk_path) if not self.symbolizer_path: log_exit("Can't find llvm-symbolizer. Please set ndk path with --ndk_path option.") self.readelf = ReadElf(ndk_path) self.dso_map = {} # map from dso_path to Dso. - self.binary_cache_path = binary_cache_path + self.binary_finder = binary_finder self.with_function_name = with_function_name # Saving file names for each addr takes a lot of memory. So we store file ids in Addr, # and provide data structures connecting file id and file name here. @@ -525,19 +567,19 @@ class Addr2Nearestline(object): self.func_name_to_id = {} self.func_id_to_name = [] - def add_addr(self, dso_path, func_addr, addr): + def add_addr(self, dso_path: str, build_id: str, func_addr: int, addr: int): dso = self.dso_map.get(dso_path) if dso is None: - dso = self.dso_map[dso_path] = self.Dso() + dso = self.dso_map[dso_path] = self.Dso(build_id) if addr not in dso.addrs: dso.addrs[addr] = self.Addr(func_addr) def convert_addrs_to_lines(self): - for dso_path in self.dso_map: - self._convert_addrs_in_one_dso(dso_path, self.dso_map[dso_path]) + for dso_path, dso in self.dso_map.items(): + self._convert_addrs_in_one_dso(dso_path, dso) - def _convert_addrs_in_one_dso(self, dso_path, dso): - real_path = find_real_dso_path(dso_path, self.binary_cache_path) + def _convert_addrs_in_one_dso(self, dso_path: str, dso: Addr2Nearestline.Dso): + real_path = self.binary_finder.find_binary(dso_path, dso.build_id) if not real_path: if dso_path not in ['//anon', 'unknown', '[kernel.kallsyms]']: log_debug("Can't find dso %s" % dso_path) @@ -553,10 +595,10 @@ class Addr2Nearestline(object): self._collect_line_info(dso, real_path, range(-addr_step * 5, -addr_step * 128 - 1, -addr_step)) - def _check_debug_line_section(self, real_path): + def _check_debug_line_section(self, real_path: Path): return '.debug_line' in self.readelf.get_sections(real_path) - def _get_addr_step(self, real_path): + def _get_addr_step(self, real_path: Path): arch = self.readelf.get_arch(real_path) if arch == 'arm64': return 4 @@ -564,7 +606,8 @@ class Addr2Nearestline(object): return 2 return 1 - def _collect_line_info(self, dso, real_path, addr_shifts): + def _collect_line_info( + self, dso: Addr2Nearestline.Dso, real_path: Path, addr_shifts: List[int]): """ Use addr2line to get line info in a dso, with given addr shifts. """ # 1. Collect addrs to send to addr2line. addr_set = set() @@ -637,7 +680,7 @@ class Addr2Nearestline(object): if shifted_addr == addr_obj.func_addr: break - def _build_symbolizer_args(self, binary_path): + def _build_symbolizer_args(self, binary_path: Path) -> List[str]: args = [self.symbolizer_path, '--print-address', '--inlining', '--obj=%s' % binary_path] if self.with_function_name: args += ['--functions=linkage', '--demangle'] @@ -753,20 +796,21 @@ class SourceFileSearcher(object): class Objdump(object): """ A wrapper of objdump to disassemble code. """ - def __init__(self, ndk_path, binary_cache_path): + def __init__(self, ndk_path: Optional[str], binary_finder: BinaryFinder): self.ndk_path = ndk_path - self.binary_cache_path = binary_cache_path + self.binary_finder = binary_finder self.readelf = ReadElf(ndk_path) self.objdump_paths = {} - def get_dso_info(self, dso_path): - real_path = find_real_dso_path(dso_path, self.binary_cache_path) + def get_dso_info(self, dso_path: str, expected_build_id: Optional[str] + ) -> Optional[Tuple[str, str]]: + real_path = self.binary_finder.find_binary(dso_path, expected_build_id) if not real_path: return None arch = self.readelf.get_arch(real_path) if arch == 'unknown': return None - return (real_path, arch) + return (str(real_path), arch) def disassemble_code(self, dso_info, start_addr, addr_len): """ Disassemble [start_addr, start_addr + addr_len] of dso_path. @@ -821,11 +865,18 @@ class ReadElf(object): if not self.readelf_path: log_exit("Can't find llvm-readelf. Please set ndk path with --ndk_path option.") - def get_arch(self, elf_file_path): + @staticmethod + def is_elf_file(path: Union[Path, str]): + if os.path.isfile(path): + with open(path, 'rb') as fh: + return fh.read(4) == b'\x7fELF' + return False + + def get_arch(self, elf_file_path: Union[Path, str]): """ Get arch of an elf file. """ - if is_elf_file(elf_file_path): + if self.is_elf_file(elf_file_path): try: - output = subprocess.check_output([self.readelf_path, '-h', elf_file_path]) + output = subprocess.check_output([self.readelf_path, '-h', str(elf_file_path)]) output = bytes_to_str(output) if output.find('AArch64') != -1: return 'arm64' @@ -839,11 +890,11 @@ class ReadElf(object): pass return 'unknown' - def get_build_id(self, elf_file_path, with_padding=True): + def get_build_id(self, elf_file_path: Union[Path, str], with_padding=True) -> str: """ Get build id of an elf file. """ - if is_elf_file(elf_file_path): + if self.is_elf_file(elf_file_path): try: - output = subprocess.check_output([self.readelf_path, '-n', elf_file_path]) + output = subprocess.check_output([self.readelf_path, '-n', str(elf_file_path)]) output = bytes_to_str(output) result = re.search(r'Build ID:\s*(\S+)', output) if result: @@ -864,12 +915,12 @@ class ReadElf(object): build_id = build_id[:40] return '0x' + build_id - def get_sections(self, elf_file_path): + def get_sections(self, elf_file_path: Union[Path, str]) -> List[str]: """ Get sections of an elf file. """ - section_names = [] - if is_elf_file(elf_file_path): + section_names: List[str] = [] + if self.is_elf_file(elf_file_path): try: - output = subprocess.check_output([self.readelf_path, '-SW', elf_file_path]) + output = subprocess.check_output([self.readelf_path, '-SW', str(elf_file_path)]) output = bytes_to_str(output) for line in output.split('\n'): # Parse line like:" [ 1] .note.android.ident NOTE 0000000000400190 ...". |