diff options
Diffstat (limited to 'simpleperf/scripts/simpleperf_utils.py')
-rw-r--r-- | simpleperf/scripts/simpleperf_utils.py | 125 |
1 files changed, 113 insertions, 12 deletions
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py index f8d50dca..e536b1b5 100644 --- a/simpleperf/scripts/simpleperf_utils.py +++ b/simpleperf/scripts/simpleperf_utils.py @@ -31,7 +31,7 @@ import shutil import subprocess import sys import time -from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union +from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, TextIO NDK_ERROR_MESSAGE = "Please install the Android NDK (https://developer.android.com/studio/projects/install-ndk), then set NDK path with --ndk_path option." @@ -360,6 +360,8 @@ class AdbHelper(object): return 'x86_64' if '86' in output: return 'x86' + if 'riscv64' in output: + return 'riscv64' log_fatal('unsupported architecture: %s' % output.strip()) return '' @@ -787,6 +789,24 @@ class SourceFileSearcher(object): return os.path.join(best_matched_rparent[::-1], file_name) +class AddrRange: + def __init__(self, start: int, len: int): + self.start = start + self.len = len + + @property + def end(self) -> int: + return self.start + self.len + + def is_in_range(self, addr: int) -> bool: + return addr >= self.start and addr < self.end + + +class Disassembly: + def __init__(self): + self.lines: List[Tuple[str, int]] = [] + + class Objdump(object): """ A wrapper of objdump to disassemble code. """ @@ -806,9 +826,8 @@ class Objdump(object): return None return (str(real_path), arch) - def disassemble_code(self, dso_info, start_addr, addr_len) -> List[Tuple[str, int]]: - """ Disassemble [start_addr, start_addr + addr_len] of dso_path. - Return a list of pair (disassemble_code_line, addr). + def disassemble_function(self, dso_info, addr_range: AddrRange) -> Optional[Disassembly]: + """ Disassemble code for an addr range in a binary. """ real_path, arch = dso_info objdump_path = self.objdump_paths.get(arch) @@ -818,15 +837,16 @@ class Objdump(object): log_exit("Can't find llvm-objdump." + NDK_ERROR_MESSAGE) self.objdump_paths[arch] = objdump_path - # 3. Run objdump. + # Run objdump. args = [objdump_path, '-dlC', '--no-show-raw-insn', - '--start-address=0x%x' % start_addr, - '--stop-address=0x%x' % (start_addr + addr_len), + '--start-address=0x%x' % addr_range.start, + '--stop-address=0x%x' % (addr_range.end), real_path] if arch == 'arm' and 'llvm-objdump' in objdump_path: args += ['--print-imm-hex'] + logging.debug('disassembling: %s', ' '.join(args)) try: - subproc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + subproc = subprocess.Popen(args, stdout=subprocess.PIPE) (stdoutdata, _) = subproc.communicate() stdoutdata = bytes_to_str(stdoutdata) except OSError: @@ -834,7 +854,7 @@ class Objdump(object): if not stdoutdata: return None - result = [] + result = Disassembly() for line in stdoutdata.split('\n'): line = line.rstrip() # Remove '\r' on Windows. items = line.split(':', 1) @@ -842,9 +862,81 @@ class Objdump(object): addr = int(items[0], 16) except ValueError: addr = 0 - result.append((line, addr)) + result.lines.append((line, addr)) return result + def disassemble_functions(self, dso_info, sorted_addr_ranges: List[AddrRange] + ) -> Optional[List[Disassembly]]: + """ Disassemble code for multiple addr ranges in a binary. sorted_addr_ranges should be + sorted by addr_range.start. + """ + if not sorted_addr_ranges: + return [] + real_path, arch = dso_info + objdump_path = self.objdump_paths.get(arch) + if not objdump_path: + objdump_path = ToolFinder.find_tool_path('llvm-objdump', self.ndk_path, arch) + if not objdump_path: + log_exit("Can't find llvm-objdump." + NDK_ERROR_MESSAGE) + self.objdump_paths[arch] = objdump_path + + # Run objdump. + start_addr = sorted_addr_ranges[0].start + stop_addr = max(addr_range.end for addr_range in sorted_addr_ranges) + args = [objdump_path, '-dlC', '--no-show-raw-insn', + '--start-address=0x%x' % start_addr, + '--stop-address=0x%x' % stop_addr, + real_path] + if arch == 'arm' and 'llvm-objdump' in objdump_path: + args += ['--print-imm-hex'] + try: + proc = subprocess.Popen(args, stdout=subprocess.PIPE, text=True) + result = self._parse_disassembly_for_functions(proc.stdout, sorted_addr_ranges) + proc.wait() + except OSError: + return None + return result + + def _parse_disassembly_for_functions(self, fh: TextIO, sorted_addr_ranges: List[AddrRange]) -> Optional[List[Disassembly]]: + current_id = 0 + in_range = False + result = [Disassembly() for _ in sorted_addr_ranges] + while True: + line = fh.readline() + if not line: + break + line = line.rstrip() # Remove '\r\n'. + addr = self._get_addr_from_disassembly_line(line) + if current_id >= len(sorted_addr_ranges): + continue + if addr: + if in_range and not sorted_addr_ranges[current_id].is_in_range(addr): + in_range = False + if not in_range: + # Skip addr ranges before the current address. + while current_id < len(sorted_addr_ranges) and sorted_addr_ranges[current_id].end <= addr: + current_id += 1 + if current_id < len(sorted_addr_ranges) and sorted_addr_ranges[current_id].is_in_range(addr): + in_range = True + if in_range: + result[current_id].lines.append((line, addr)) + return result + + def _get_addr_from_disassembly_line(self, line: str) -> int: + # line may be an instruction, like: " 24a469c: stp x29, x30, [sp, #-0x60]!" or + # "ffffffc0085d9664: paciasp". + # line may be a function start point, like "00000000024a4698 <DoWork()>:". + items = line.strip().split() + if not items: + return 0 + s = items[0] + if s.endswith(':'): + s = s[:-1] + try: + return int(s, 16) + except ValueError: + return 0 + class ReadElf(object): """ A wrapper of readelf. """ @@ -875,6 +967,8 @@ class ReadElf(object): return 'x86_64' if output.find('80386') != -1: return 'x86' + if output.find('RISC-V') != -1: + return 'riscv64' except subprocess.CalledProcessError: pass return 'unknown' @@ -1003,6 +1097,7 @@ class ArgParseFormatter( @dataclass class ReportLibOptions: show_art_frames: bool + remove_method: List[str] trace_offcpu: str proguard_mapping_files: List[str] sample_filters: List[str] @@ -1028,6 +1123,8 @@ class BaseArgumentParser(argparse.ArgumentParser): parser.add_argument('--show-art-frames', '--show_art_frames', action=argparse.BooleanOptionalAction, default=default_show_art_frames, help='Show frames of internal methods in the ART Java interpreter.') + parser.add_argument('--remove-method', nargs='+', metavar='method_name_regex', + help='remove methods with name containing the regular expression') parser.add_argument( '--trace-offcpu', choices=['on-cpu', 'off-cpu', 'on-off-cpu', 'mixed-on-off-cpu'], help="""Set report mode for profiles recorded with --trace-offcpu option. All possible @@ -1047,6 +1144,8 @@ class BaseArgumentParser(argparse.ArgumentParser): self, group: Optional[Any] = None, with_pid_shortcut: bool = True): if not group: group = self.add_argument_group('Sample filter options') + group.add_argument('--cpu', nargs='+', help="""only include samples for the selected cpus. + cpu can be a number like 1, or a range like 0-3""") group.add_argument('--exclude-pid', metavar='pid', nargs='+', type=int, help='exclude samples for selected processes') group.add_argument('--exclude-tid', metavar='tid', nargs='+', type=int, @@ -1084,6 +1183,8 @@ class BaseArgumentParser(argparse.ArgumentParser): def _build_sample_filter(self, args: argparse.Namespace) -> List[str]: """ Build sample filters, which can be passed to ReportLib.SetSampleFilter(). """ filters = [] + if args.cpu: + filters.extend(['--cpu', ','.join(args.cpu)]) if args.exclude_pid: filters.extend(['--exclude-pid', ','.join(str(pid) for pid in args.exclude_pid)]) if args.exclude_tid: @@ -1123,8 +1224,8 @@ class BaseArgumentParser(argparse.ArgumentParser): if self.has_report_lib_options: sample_filters = self._build_sample_filter(namespace) report_lib_options = ReportLibOptions( - namespace.show_art_frames, namespace.trace_offcpu, namespace.proguard_mapping_file, - sample_filters, namespace.aggregate_threads) + namespace.show_art_frames, namespace.remove_method, namespace.trace_offcpu, + namespace.proguard_mapping_file, sample_filters, namespace.aggregate_threads) setattr(namespace, 'report_lib_options', report_lib_options) if not Log.initialized: |