1*288bf522SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*288bf522SAndroid Build Coastguard Worker# 3*288bf522SAndroid Build Coastguard Worker# Copyright (C) 2016 The Android Open Source Project 4*288bf522SAndroid Build Coastguard Worker# 5*288bf522SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*288bf522SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*288bf522SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*288bf522SAndroid Build Coastguard Worker# 9*288bf522SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*288bf522SAndroid Build Coastguard Worker# 11*288bf522SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*288bf522SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*288bf522SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*288bf522SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*288bf522SAndroid Build Coastguard Worker# limitations under the License. 16*288bf522SAndroid Build Coastguard Worker# 17*288bf522SAndroid Build Coastguard Worker 18*288bf522SAndroid Build Coastguard Worker"""utils.py: export utility functions. 19*288bf522SAndroid Build Coastguard Worker""" 20*288bf522SAndroid Build Coastguard Worker 21*288bf522SAndroid Build Coastguard Workerfrom __future__ import annotations 22*288bf522SAndroid Build Coastguard Workerimport argparse 23*288bf522SAndroid Build Coastguard Workerfrom concurrent.futures import Future, ThreadPoolExecutor 24*288bf522SAndroid Build Coastguard Workerfrom dataclasses import dataclass 25*288bf522SAndroid Build Coastguard Workerimport logging 26*288bf522SAndroid Build Coastguard Workerimport os 27*288bf522SAndroid Build Coastguard Workerimport os.path 28*288bf522SAndroid Build Coastguard Workerfrom pathlib import Path 29*288bf522SAndroid Build Coastguard Workerimport re 30*288bf522SAndroid Build Coastguard Workerimport shutil 31*288bf522SAndroid Build Coastguard Workerimport subprocess 32*288bf522SAndroid Build Coastguard Workerimport sys 33*288bf522SAndroid Build Coastguard Workerimport time 34*288bf522SAndroid Build Coastguard Workerfrom typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, TextIO 35*288bf522SAndroid Build Coastguard Worker 36*288bf522SAndroid Build Coastguard Worker 37*288bf522SAndroid Build Coastguard WorkerNDK_ERROR_MESSAGE = "Please install the Android NDK (https://developer.android.com/studio/projects/install-ndk), then set NDK path with --ndk_path option." 38*288bf522SAndroid Build Coastguard Worker 39*288bf522SAndroid Build Coastguard Worker 40*288bf522SAndroid Build Coastguard Workerdef get_script_dir() -> str: 41*288bf522SAndroid Build Coastguard Worker return os.path.dirname(os.path.realpath(__file__)) 42*288bf522SAndroid Build Coastguard Worker 43*288bf522SAndroid Build Coastguard Worker 44*288bf522SAndroid Build Coastguard Workerdef is_windows() -> bool: 45*288bf522SAndroid Build Coastguard Worker return sys.platform == 'win32' or sys.platform == 'cygwin' 46*288bf522SAndroid Build Coastguard Worker 47*288bf522SAndroid Build Coastguard Worker 48*288bf522SAndroid Build Coastguard Workerdef is_darwin() -> bool: 49*288bf522SAndroid Build Coastguard Worker return sys.platform == 'darwin' 50*288bf522SAndroid Build Coastguard Worker 51*288bf522SAndroid Build Coastguard Worker 52*288bf522SAndroid Build Coastguard Workerdef get_platform() -> str: 53*288bf522SAndroid Build Coastguard Worker if is_windows(): 54*288bf522SAndroid Build Coastguard Worker return 'windows' 55*288bf522SAndroid Build Coastguard Worker if is_darwin(): 56*288bf522SAndroid Build Coastguard Worker return 'darwin' 57*288bf522SAndroid Build Coastguard Worker return 'linux' 58*288bf522SAndroid Build Coastguard Worker 59*288bf522SAndroid Build Coastguard Worker 60*288bf522SAndroid Build Coastguard Workerdef str_to_bytes(str_value: str) -> bytes: 61*288bf522SAndroid Build Coastguard Worker # In python 3, str are wide strings whereas the C api expects 8 bit strings, 62*288bf522SAndroid Build Coastguard Worker # hence we have to convert. For now using utf-8 as the encoding. 63*288bf522SAndroid Build Coastguard Worker return str_value.encode('utf-8') 64*288bf522SAndroid Build Coastguard Worker 65*288bf522SAndroid Build Coastguard Worker 66*288bf522SAndroid Build Coastguard Workerdef bytes_to_str(bytes_value: Optional[bytes]) -> str: 67*288bf522SAndroid Build Coastguard Worker if not bytes_value: 68*288bf522SAndroid Build Coastguard Worker return '' 69*288bf522SAndroid Build Coastguard Worker return bytes_value.decode('utf-8') 70*288bf522SAndroid Build Coastguard Worker 71*288bf522SAndroid Build Coastguard Worker 72*288bf522SAndroid Build Coastguard Workerdef get_target_binary_path(arch: str, binary_name: str) -> str: 73*288bf522SAndroid Build Coastguard Worker if arch == 'aarch64': 74*288bf522SAndroid Build Coastguard Worker arch = 'arm64' 75*288bf522SAndroid Build Coastguard Worker arch_dir = os.path.join(get_script_dir(), "bin", "android", arch) 76*288bf522SAndroid Build Coastguard Worker if not os.path.isdir(arch_dir): 77*288bf522SAndroid Build Coastguard Worker log_fatal("can't find arch directory: %s" % arch_dir) 78*288bf522SAndroid Build Coastguard Worker binary_path = os.path.join(arch_dir, binary_name) 79*288bf522SAndroid Build Coastguard Worker if not os.path.isfile(binary_path): 80*288bf522SAndroid Build Coastguard Worker log_fatal("can't find binary: %s" % binary_path) 81*288bf522SAndroid Build Coastguard Worker return binary_path 82*288bf522SAndroid Build Coastguard Worker 83*288bf522SAndroid Build Coastguard Worker 84*288bf522SAndroid Build Coastguard Workerdef get_host_binary_path(binary_name: str) -> str: 85*288bf522SAndroid Build Coastguard Worker dirname = os.path.join(get_script_dir(), 'bin') 86*288bf522SAndroid Build Coastguard Worker if is_windows(): 87*288bf522SAndroid Build Coastguard Worker if binary_name.endswith('.so'): 88*288bf522SAndroid Build Coastguard Worker binary_name = binary_name[0:-3] + '.dll' 89*288bf522SAndroid Build Coastguard Worker elif '.' not in binary_name: 90*288bf522SAndroid Build Coastguard Worker binary_name += '.exe' 91*288bf522SAndroid Build Coastguard Worker dirname = os.path.join(dirname, 'windows') 92*288bf522SAndroid Build Coastguard Worker elif sys.platform == 'darwin': # OSX 93*288bf522SAndroid Build Coastguard Worker if binary_name.endswith('.so'): 94*288bf522SAndroid Build Coastguard Worker binary_name = binary_name[0:-3] + '.dylib' 95*288bf522SAndroid Build Coastguard Worker dirname = os.path.join(dirname, 'darwin') 96*288bf522SAndroid Build Coastguard Worker else: 97*288bf522SAndroid Build Coastguard Worker dirname = os.path.join(dirname, 'linux') 98*288bf522SAndroid Build Coastguard Worker dirname = os.path.join(dirname, 'x86_64' if sys.maxsize > 2 ** 32 else 'x86') 99*288bf522SAndroid Build Coastguard Worker binary_path = os.path.join(dirname, binary_name) 100*288bf522SAndroid Build Coastguard Worker if not os.path.isfile(binary_path): 101*288bf522SAndroid Build Coastguard Worker log_fatal("can't find binary: %s" % binary_path) 102*288bf522SAndroid Build Coastguard Worker return binary_path 103*288bf522SAndroid Build Coastguard Worker 104*288bf522SAndroid Build Coastguard Worker 105*288bf522SAndroid Build Coastguard Workerdef is_executable_available(executable: str, option='--help') -> bool: 106*288bf522SAndroid Build Coastguard Worker """ Run an executable to see if it exists. """ 107*288bf522SAndroid Build Coastguard Worker try: 108*288bf522SAndroid Build Coastguard Worker subproc = subprocess.Popen([executable, option], stdout=subprocess.PIPE, 109*288bf522SAndroid Build Coastguard Worker stderr=subprocess.PIPE) 110*288bf522SAndroid Build Coastguard Worker subproc.communicate() 111*288bf522SAndroid Build Coastguard Worker return subproc.returncode == 0 112*288bf522SAndroid Build Coastguard Worker except OSError: 113*288bf522SAndroid Build Coastguard Worker return False 114*288bf522SAndroid Build Coastguard Worker 115*288bf522SAndroid Build Coastguard Worker 116*288bf522SAndroid Build Coastguard Workerclass ToolFinder: 117*288bf522SAndroid Build Coastguard Worker """ Find tools in ndk or sdk. """ 118*288bf522SAndroid Build Coastguard Worker DEFAULT_SDK_PATH = { 119*288bf522SAndroid Build Coastguard Worker 'darwin': 'Library/Android/sdk', 120*288bf522SAndroid Build Coastguard Worker 'linux': 'Android/Sdk', 121*288bf522SAndroid Build Coastguard Worker 'windows': 'AppData/Local/Android/sdk', 122*288bf522SAndroid Build Coastguard Worker } 123*288bf522SAndroid Build Coastguard Worker 124*288bf522SAndroid Build Coastguard Worker EXPECTED_TOOLS = { 125*288bf522SAndroid Build Coastguard Worker 'adb': { 126*288bf522SAndroid Build Coastguard Worker 'is_binutils': False, 127*288bf522SAndroid Build Coastguard Worker 'test_option': 'version', 128*288bf522SAndroid Build Coastguard Worker 'path_in_sdk': 'platform-tools/adb', 129*288bf522SAndroid Build Coastguard Worker }, 130*288bf522SAndroid Build Coastguard Worker 'llvm-objdump': { 131*288bf522SAndroid Build Coastguard Worker 'is_binutils': False, 132*288bf522SAndroid Build Coastguard Worker 'path_in_ndk': 133*288bf522SAndroid Build Coastguard Worker lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-objdump' % platform, 134*288bf522SAndroid Build Coastguard Worker }, 135*288bf522SAndroid Build Coastguard Worker 'llvm-readelf': { 136*288bf522SAndroid Build Coastguard Worker 'is_binutils': False, 137*288bf522SAndroid Build Coastguard Worker 'path_in_ndk': 138*288bf522SAndroid Build Coastguard Worker lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-readelf' % platform, 139*288bf522SAndroid Build Coastguard Worker }, 140*288bf522SAndroid Build Coastguard Worker 'llvm-symbolizer': { 141*288bf522SAndroid Build Coastguard Worker 'is_binutils': False, 142*288bf522SAndroid Build Coastguard Worker 'path_in_ndk': 143*288bf522SAndroid Build Coastguard Worker lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-symbolizer' % platform, 144*288bf522SAndroid Build Coastguard Worker }, 145*288bf522SAndroid Build Coastguard Worker 'llvm-strip': { 146*288bf522SAndroid Build Coastguard Worker 'is_binutils': False, 147*288bf522SAndroid Build Coastguard Worker 'path_in_ndk': 148*288bf522SAndroid Build Coastguard Worker lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-strip' % platform, 149*288bf522SAndroid Build Coastguard Worker }, 150*288bf522SAndroid Build Coastguard Worker } 151*288bf522SAndroid Build Coastguard Worker 152*288bf522SAndroid Build Coastguard Worker @classmethod 153*288bf522SAndroid Build Coastguard Worker def find_ndk_and_sdk_paths(cls, ndk_path: Optional[str] = None 154*288bf522SAndroid Build Coastguard Worker ) -> Iterator[Tuple[Optional[str], Optional[str]]]: 155*288bf522SAndroid Build Coastguard Worker # Use the given ndk path. 156*288bf522SAndroid Build Coastguard Worker if ndk_path and os.path.isdir(ndk_path): 157*288bf522SAndroid Build Coastguard Worker ndk_path = os.path.abspath(ndk_path) 158*288bf522SAndroid Build Coastguard Worker yield ndk_path, cls.find_sdk_path(ndk_path) 159*288bf522SAndroid Build Coastguard Worker # Find ndk in the parent directory containing simpleperf scripts. 160*288bf522SAndroid Build Coastguard Worker ndk_path = os.path.dirname(os.path.abspath(get_script_dir())) 161*288bf522SAndroid Build Coastguard Worker yield ndk_path, cls.find_sdk_path(ndk_path) 162*288bf522SAndroid Build Coastguard Worker # Find ndk in the default sdk installation path. 163*288bf522SAndroid Build Coastguard Worker if is_windows(): 164*288bf522SAndroid Build Coastguard Worker home = os.environ.get('HOMEDRIVE') + os.environ.get('HOMEPATH') 165*288bf522SAndroid Build Coastguard Worker else: 166*288bf522SAndroid Build Coastguard Worker home = os.environ.get('HOME') 167*288bf522SAndroid Build Coastguard Worker if home: 168*288bf522SAndroid Build Coastguard Worker platform = get_platform() 169*288bf522SAndroid Build Coastguard Worker sdk_path = os.path.join(home, cls.DEFAULT_SDK_PATH[platform].replace('/', os.sep)) 170*288bf522SAndroid Build Coastguard Worker if os.path.isdir(sdk_path): 171*288bf522SAndroid Build Coastguard Worker path = os.path.join(sdk_path, 'ndk') 172*288bf522SAndroid Build Coastguard Worker if os.path.isdir(path): 173*288bf522SAndroid Build Coastguard Worker # Android Studio can install multiple ndk versions in 'ndk'. 174*288bf522SAndroid Build Coastguard Worker # Find the newest one. 175*288bf522SAndroid Build Coastguard Worker ndk_version = None 176*288bf522SAndroid Build Coastguard Worker for name in os.listdir(path): 177*288bf522SAndroid Build Coastguard Worker if not ndk_version or ndk_version < name: 178*288bf522SAndroid Build Coastguard Worker ndk_version = name 179*288bf522SAndroid Build Coastguard Worker if ndk_version: 180*288bf522SAndroid Build Coastguard Worker yield os.path.join(path, ndk_version), sdk_path 181*288bf522SAndroid Build Coastguard Worker ndk_path = os.path.join(sdk_path, 'ndk-bundle') 182*288bf522SAndroid Build Coastguard Worker if os.path.isdir(ndk_path): 183*288bf522SAndroid Build Coastguard Worker yield ndk_path, sdk_path 184*288bf522SAndroid Build Coastguard Worker 185*288bf522SAndroid Build Coastguard Worker @classmethod 186*288bf522SAndroid Build Coastguard Worker def find_sdk_path(cls, ndk_path: str) -> Optional[str]: 187*288bf522SAndroid Build Coastguard Worker path = ndk_path 188*288bf522SAndroid Build Coastguard Worker for _ in range(2): 189*288bf522SAndroid Build Coastguard Worker path = os.path.dirname(path) 190*288bf522SAndroid Build Coastguard Worker if os.path.isdir(os.path.join(path, 'platform-tools')): 191*288bf522SAndroid Build Coastguard Worker return path 192*288bf522SAndroid Build Coastguard Worker return None 193*288bf522SAndroid Build Coastguard Worker 194*288bf522SAndroid Build Coastguard Worker @classmethod 195*288bf522SAndroid Build Coastguard Worker def _get_binutils_path_in_ndk(cls, toolname: str, arch: Optional[str], platform: str 196*288bf522SAndroid Build Coastguard Worker ) -> Tuple[str, str]: 197*288bf522SAndroid Build Coastguard Worker if not arch: 198*288bf522SAndroid Build Coastguard Worker arch = 'arm64' 199*288bf522SAndroid Build Coastguard Worker if arch == 'arm64': 200*288bf522SAndroid Build Coastguard Worker name = 'aarch64-linux-android-' + toolname 201*288bf522SAndroid Build Coastguard Worker elif arch == 'arm': 202*288bf522SAndroid Build Coastguard Worker name = 'arm-linux-androideabi-' + toolname 203*288bf522SAndroid Build Coastguard Worker elif arch == 'x86_64': 204*288bf522SAndroid Build Coastguard Worker name = 'x86_64-linux-android-' + toolname 205*288bf522SAndroid Build Coastguard Worker elif arch == 'x86': 206*288bf522SAndroid Build Coastguard Worker name = 'i686-linux-android-' + toolname 207*288bf522SAndroid Build Coastguard Worker else: 208*288bf522SAndroid Build Coastguard Worker log_fatal('unexpected arch %s' % arch) 209*288bf522SAndroid Build Coastguard Worker path = 'toolchains/llvm/prebuilt/%s-x86_64/bin/%s' % (platform, name) 210*288bf522SAndroid Build Coastguard Worker return (name, path) 211*288bf522SAndroid Build Coastguard Worker 212*288bf522SAndroid Build Coastguard Worker @classmethod 213*288bf522SAndroid Build Coastguard Worker def find_tool_path(cls, toolname: str, ndk_path: Optional[str] = None, 214*288bf522SAndroid Build Coastguard Worker arch: Optional[str] = None) -> Optional[str]: 215*288bf522SAndroid Build Coastguard Worker tool_info = cls.EXPECTED_TOOLS.get(toolname) 216*288bf522SAndroid Build Coastguard Worker if not tool_info: 217*288bf522SAndroid Build Coastguard Worker return None 218*288bf522SAndroid Build Coastguard Worker 219*288bf522SAndroid Build Coastguard Worker is_binutils = tool_info['is_binutils'] 220*288bf522SAndroid Build Coastguard Worker test_option = tool_info.get('test_option', '--help') 221*288bf522SAndroid Build Coastguard Worker platform = get_platform() 222*288bf522SAndroid Build Coastguard Worker 223*288bf522SAndroid Build Coastguard Worker # Find tool in clang prebuilts in Android platform. 224*288bf522SAndroid Build Coastguard Worker if toolname.startswith('llvm-') and platform == 'linux' and get_script_dir().endswith( 225*288bf522SAndroid Build Coastguard Worker 'system/extras/simpleperf/scripts'): 226*288bf522SAndroid Build Coastguard Worker path = str( 227*288bf522SAndroid Build Coastguard Worker Path(get_script_dir()).parents[3] / 'prebuilts' / 'clang' / 'host' / 'linux-x86' / 228*288bf522SAndroid Build Coastguard Worker 'llvm-binutils-stable' / toolname) 229*288bf522SAndroid Build Coastguard Worker if is_executable_available(path, test_option): 230*288bf522SAndroid Build Coastguard Worker return path 231*288bf522SAndroid Build Coastguard Worker 232*288bf522SAndroid Build Coastguard Worker # Find tool in NDK or SDK. 233*288bf522SAndroid Build Coastguard Worker path_in_ndk = None 234*288bf522SAndroid Build Coastguard Worker path_in_sdk = None 235*288bf522SAndroid Build Coastguard Worker if is_binutils: 236*288bf522SAndroid Build Coastguard Worker toolname_with_arch, path_in_ndk = cls._get_binutils_path_in_ndk( 237*288bf522SAndroid Build Coastguard Worker toolname, arch, platform) 238*288bf522SAndroid Build Coastguard Worker else: 239*288bf522SAndroid Build Coastguard Worker toolname_with_arch = toolname 240*288bf522SAndroid Build Coastguard Worker if 'path_in_ndk' in tool_info: 241*288bf522SAndroid Build Coastguard Worker path_in_ndk = tool_info['path_in_ndk'](platform) 242*288bf522SAndroid Build Coastguard Worker elif 'path_in_sdk' in tool_info: 243*288bf522SAndroid Build Coastguard Worker path_in_sdk = tool_info['path_in_sdk'] 244*288bf522SAndroid Build Coastguard Worker if path_in_ndk: 245*288bf522SAndroid Build Coastguard Worker path_in_ndk = path_in_ndk.replace('/', os.sep) 246*288bf522SAndroid Build Coastguard Worker elif path_in_sdk: 247*288bf522SAndroid Build Coastguard Worker path_in_sdk = path_in_sdk.replace('/', os.sep) 248*288bf522SAndroid Build Coastguard Worker 249*288bf522SAndroid Build Coastguard Worker for ndk_dir, sdk_dir in cls.find_ndk_and_sdk_paths(ndk_path): 250*288bf522SAndroid Build Coastguard Worker if path_in_ndk and ndk_dir: 251*288bf522SAndroid Build Coastguard Worker path = os.path.join(ndk_dir, path_in_ndk) 252*288bf522SAndroid Build Coastguard Worker if is_executable_available(path, test_option): 253*288bf522SAndroid Build Coastguard Worker return path 254*288bf522SAndroid Build Coastguard Worker elif path_in_sdk and sdk_dir: 255*288bf522SAndroid Build Coastguard Worker path = os.path.join(sdk_dir, path_in_sdk) 256*288bf522SAndroid Build Coastguard Worker if is_executable_available(path, test_option): 257*288bf522SAndroid Build Coastguard Worker return path 258*288bf522SAndroid Build Coastguard Worker 259*288bf522SAndroid Build Coastguard Worker # Find tool in $PATH. 260*288bf522SAndroid Build Coastguard Worker if is_executable_available(toolname_with_arch, test_option): 261*288bf522SAndroid Build Coastguard Worker return toolname_with_arch 262*288bf522SAndroid Build Coastguard Worker 263*288bf522SAndroid Build Coastguard Worker # Find tool without arch in $PATH. 264*288bf522SAndroid Build Coastguard Worker if is_binutils and tool_info.get('accept_tool_without_arch'): 265*288bf522SAndroid Build Coastguard Worker if is_executable_available(toolname, test_option): 266*288bf522SAndroid Build Coastguard Worker return toolname 267*288bf522SAndroid Build Coastguard Worker return None 268*288bf522SAndroid Build Coastguard Worker 269*288bf522SAndroid Build Coastguard Worker 270*288bf522SAndroid Build Coastguard Workerclass AdbHelper(object): 271*288bf522SAndroid Build Coastguard Worker def __init__(self, enable_switch_to_root: bool = True): 272*288bf522SAndroid Build Coastguard Worker adb_path = ToolFinder.find_tool_path('adb') 273*288bf522SAndroid Build Coastguard Worker if not adb_path: 274*288bf522SAndroid Build Coastguard Worker log_exit("Can't find adb in PATH environment.") 275*288bf522SAndroid Build Coastguard Worker self.adb_path: str = adb_path 276*288bf522SAndroid Build Coastguard Worker self.enable_switch_to_root = enable_switch_to_root 277*288bf522SAndroid Build Coastguard Worker self.serial_number: Optional[str] = None 278*288bf522SAndroid Build Coastguard Worker 279*288bf522SAndroid Build Coastguard Worker def is_device_available(self) -> bool: 280*288bf522SAndroid Build Coastguard Worker return self.run_and_return_output(['shell', 'whoami'])[0] 281*288bf522SAndroid Build Coastguard Worker 282*288bf522SAndroid Build Coastguard Worker def run(self, adb_args: List[str], log_output: bool = False, log_stderr: bool = False) -> bool: 283*288bf522SAndroid Build Coastguard Worker return self.run_and_return_output(adb_args, log_output, log_stderr)[0] 284*288bf522SAndroid Build Coastguard Worker 285*288bf522SAndroid Build Coastguard Worker def run_and_return_output(self, adb_args: List[str], log_output: bool = False, 286*288bf522SAndroid Build Coastguard Worker log_stderr: bool = False) -> Tuple[bool, str]: 287*288bf522SAndroid Build Coastguard Worker adb_args = [self.adb_path] + adb_args 288*288bf522SAndroid Build Coastguard Worker logging.debug('run adb cmd: %s' % adb_args) 289*288bf522SAndroid Build Coastguard Worker env = None 290*288bf522SAndroid Build Coastguard Worker if self.serial_number: 291*288bf522SAndroid Build Coastguard Worker env = os.environ.copy() 292*288bf522SAndroid Build Coastguard Worker env['ANDROID_SERIAL'] = self.serial_number 293*288bf522SAndroid Build Coastguard Worker subproc = subprocess.Popen( 294*288bf522SAndroid Build Coastguard Worker adb_args, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 295*288bf522SAndroid Build Coastguard Worker stdout_data, stderr_data = subproc.communicate() 296*288bf522SAndroid Build Coastguard Worker stdout_data = bytes_to_str(stdout_data) 297*288bf522SAndroid Build Coastguard Worker stderr_data = bytes_to_str(stderr_data) 298*288bf522SAndroid Build Coastguard Worker returncode = subproc.returncode 299*288bf522SAndroid Build Coastguard Worker result = (returncode == 0) 300*288bf522SAndroid Build Coastguard Worker if log_output and stdout_data: 301*288bf522SAndroid Build Coastguard Worker logging.debug(stdout_data) 302*288bf522SAndroid Build Coastguard Worker if log_stderr and stderr_data: 303*288bf522SAndroid Build Coastguard Worker logging.warning(stderr_data) 304*288bf522SAndroid Build Coastguard Worker logging.debug('run adb cmd: %s [result %s]' % (adb_args, result)) 305*288bf522SAndroid Build Coastguard Worker return (result, stdout_data) 306*288bf522SAndroid Build Coastguard Worker 307*288bf522SAndroid Build Coastguard Worker def check_run(self, adb_args: List[str], log_output: bool = False): 308*288bf522SAndroid Build Coastguard Worker self.check_run_and_return_output(adb_args, log_output) 309*288bf522SAndroid Build Coastguard Worker 310*288bf522SAndroid Build Coastguard Worker def check_run_and_return_output(self, adb_args: List[str], log_output: bool = False, 311*288bf522SAndroid Build Coastguard Worker log_stderr: bool = False) -> str: 312*288bf522SAndroid Build Coastguard Worker result, stdoutdata = self.run_and_return_output(adb_args, log_output, True) 313*288bf522SAndroid Build Coastguard Worker if not result: 314*288bf522SAndroid Build Coastguard Worker log_exit('run "adb %s" failed: %s' % (adb_args, stdoutdata)) 315*288bf522SAndroid Build Coastguard Worker return stdoutdata 316*288bf522SAndroid Build Coastguard Worker 317*288bf522SAndroid Build Coastguard Worker def _unroot(self): 318*288bf522SAndroid Build Coastguard Worker result, stdoutdata = self.run_and_return_output(['shell', 'whoami']) 319*288bf522SAndroid Build Coastguard Worker if not result: 320*288bf522SAndroid Build Coastguard Worker return 321*288bf522SAndroid Build Coastguard Worker if 'root' not in stdoutdata: 322*288bf522SAndroid Build Coastguard Worker return 323*288bf522SAndroid Build Coastguard Worker logging.info('unroot adb') 324*288bf522SAndroid Build Coastguard Worker self.run(['unroot']) 325*288bf522SAndroid Build Coastguard Worker time.sleep(1) 326*288bf522SAndroid Build Coastguard Worker self.run(['wait-for-device']) 327*288bf522SAndroid Build Coastguard Worker 328*288bf522SAndroid Build Coastguard Worker def switch_to_root(self) -> bool: 329*288bf522SAndroid Build Coastguard Worker if not self.enable_switch_to_root: 330*288bf522SAndroid Build Coastguard Worker self._unroot() 331*288bf522SAndroid Build Coastguard Worker return False 332*288bf522SAndroid Build Coastguard Worker result, stdoutdata = self.run_and_return_output(['shell', 'whoami']) 333*288bf522SAndroid Build Coastguard Worker if not result: 334*288bf522SAndroid Build Coastguard Worker return False 335*288bf522SAndroid Build Coastguard Worker if 'root' in stdoutdata: 336*288bf522SAndroid Build Coastguard Worker return True 337*288bf522SAndroid Build Coastguard Worker build_type = self.get_property('ro.build.type') 338*288bf522SAndroid Build Coastguard Worker if build_type == 'user': 339*288bf522SAndroid Build Coastguard Worker return False 340*288bf522SAndroid Build Coastguard Worker self.run(['root']) 341*288bf522SAndroid Build Coastguard Worker time.sleep(1) 342*288bf522SAndroid Build Coastguard Worker self.run(['wait-for-device']) 343*288bf522SAndroid Build Coastguard Worker result, stdoutdata = self.run_and_return_output(['shell', 'whoami']) 344*288bf522SAndroid Build Coastguard Worker return result and 'root' in stdoutdata 345*288bf522SAndroid Build Coastguard Worker 346*288bf522SAndroid Build Coastguard Worker def get_property(self, name: str) -> Optional[str]: 347*288bf522SAndroid Build Coastguard Worker result, stdoutdata = self.run_and_return_output(['shell', 'getprop', name]) 348*288bf522SAndroid Build Coastguard Worker return stdoutdata.strip() if result else None 349*288bf522SAndroid Build Coastguard Worker 350*288bf522SAndroid Build Coastguard Worker def set_property(self, name: str, value: str) -> bool: 351*288bf522SAndroid Build Coastguard Worker return self.run(['shell', 'setprop', name, value]) 352*288bf522SAndroid Build Coastguard Worker 353*288bf522SAndroid Build Coastguard Worker def get_device_arch(self) -> str: 354*288bf522SAndroid Build Coastguard Worker output = self.check_run_and_return_output(['shell', 'uname', '-m']) 355*288bf522SAndroid Build Coastguard Worker if 'aarch64' in output: 356*288bf522SAndroid Build Coastguard Worker return 'arm64' 357*288bf522SAndroid Build Coastguard Worker if 'arm' in output: 358*288bf522SAndroid Build Coastguard Worker return 'arm' 359*288bf522SAndroid Build Coastguard Worker if 'x86_64' in output: 360*288bf522SAndroid Build Coastguard Worker return 'x86_64' 361*288bf522SAndroid Build Coastguard Worker if '86' in output: 362*288bf522SAndroid Build Coastguard Worker return 'x86' 363*288bf522SAndroid Build Coastguard Worker if 'riscv64' in output: 364*288bf522SAndroid Build Coastguard Worker return 'riscv64' 365*288bf522SAndroid Build Coastguard Worker log_fatal('unsupported architecture: %s' % output.strip()) 366*288bf522SAndroid Build Coastguard Worker return '' 367*288bf522SAndroid Build Coastguard Worker 368*288bf522SAndroid Build Coastguard Worker def get_android_version(self) -> int: 369*288bf522SAndroid Build Coastguard Worker """ Get Android version on device, like 7 is for Android N, 8 is for Android O.""" 370*288bf522SAndroid Build Coastguard Worker def parse_version(s: str) -> int: 371*288bf522SAndroid Build Coastguard Worker if not s: 372*288bf522SAndroid Build Coastguard Worker return 0 373*288bf522SAndroid Build Coastguard Worker if s[0].isdigit(): 374*288bf522SAndroid Build Coastguard Worker i = 1 375*288bf522SAndroid Build Coastguard Worker while i < len(s) and s[i].isdigit(): 376*288bf522SAndroid Build Coastguard Worker i += 1 377*288bf522SAndroid Build Coastguard Worker return int(s[:i]) 378*288bf522SAndroid Build Coastguard Worker else: 379*288bf522SAndroid Build Coastguard Worker c = s[0].upper() 380*288bf522SAndroid Build Coastguard Worker if c.isupper() and 'L' <= c <= 'V': 381*288bf522SAndroid Build Coastguard Worker return ord(c) - ord('L') + 5 382*288bf522SAndroid Build Coastguard Worker return 0 383*288bf522SAndroid Build Coastguard Worker 384*288bf522SAndroid Build Coastguard Worker android_version = 0 385*288bf522SAndroid Build Coastguard Worker s = self.get_property('ro.build.version.codename') 386*288bf522SAndroid Build Coastguard Worker if s != 'REL': 387*288bf522SAndroid Build Coastguard Worker android_version = parse_version(s) 388*288bf522SAndroid Build Coastguard Worker if android_version == 0: 389*288bf522SAndroid Build Coastguard Worker s = self.get_property('ro.build.version.release') 390*288bf522SAndroid Build Coastguard Worker android_version = parse_version(s) 391*288bf522SAndroid Build Coastguard Worker if android_version == 0: 392*288bf522SAndroid Build Coastguard Worker s = self.get_property('ro.build.version.sdk') 393*288bf522SAndroid Build Coastguard Worker if int(s) >= 35: 394*288bf522SAndroid Build Coastguard Worker android_version = 15 395*288bf522SAndroid Build Coastguard Worker return android_version 396*288bf522SAndroid Build Coastguard Worker 397*288bf522SAndroid Build Coastguard Worker 398*288bf522SAndroid Build Coastguard Workerdef flatten_arg_list(arg_list: List[List[str]]) -> List[str]: 399*288bf522SAndroid Build Coastguard Worker res = [] 400*288bf522SAndroid Build Coastguard Worker if arg_list: 401*288bf522SAndroid Build Coastguard Worker for items in arg_list: 402*288bf522SAndroid Build Coastguard Worker res += items 403*288bf522SAndroid Build Coastguard Worker return res 404*288bf522SAndroid Build Coastguard Worker 405*288bf522SAndroid Build Coastguard Worker 406*288bf522SAndroid Build Coastguard Workerdef remove(dir_or_file: Union[Path, str]): 407*288bf522SAndroid Build Coastguard Worker if os.path.isfile(dir_or_file): 408*288bf522SAndroid Build Coastguard Worker os.remove(dir_or_file) 409*288bf522SAndroid Build Coastguard Worker elif os.path.isdir(dir_or_file): 410*288bf522SAndroid Build Coastguard Worker shutil.rmtree(dir_or_file, ignore_errors=True) 411*288bf522SAndroid Build Coastguard Worker 412*288bf522SAndroid Build Coastguard Worker 413*288bf522SAndroid Build Coastguard Workerdef open_report_in_browser(report_path: str): 414*288bf522SAndroid Build Coastguard Worker if is_darwin(): 415*288bf522SAndroid Build Coastguard Worker # On darwin 10.12.6, webbrowser can't open browser, so try `open` cmd first. 416*288bf522SAndroid Build Coastguard Worker try: 417*288bf522SAndroid Build Coastguard Worker subprocess.check_call(['open', report_path]) 418*288bf522SAndroid Build Coastguard Worker return 419*288bf522SAndroid Build Coastguard Worker except subprocess.CalledProcessError: 420*288bf522SAndroid Build Coastguard Worker pass 421*288bf522SAndroid Build Coastguard Worker import webbrowser 422*288bf522SAndroid Build Coastguard Worker try: 423*288bf522SAndroid Build Coastguard Worker # Try to open the report with Chrome 424*288bf522SAndroid Build Coastguard Worker browser = webbrowser.get('google-chrome') 425*288bf522SAndroid Build Coastguard Worker browser.open(report_path, new=0, autoraise=True) 426*288bf522SAndroid Build Coastguard Worker except webbrowser.Error: 427*288bf522SAndroid Build Coastguard Worker # webbrowser.get() doesn't work well on darwin/windows. 428*288bf522SAndroid Build Coastguard Worker webbrowser.open_new_tab(report_path) 429*288bf522SAndroid Build Coastguard Worker 430*288bf522SAndroid Build Coastguard Worker 431*288bf522SAndroid Build Coastguard Workerclass BinaryFinder: 432*288bf522SAndroid Build Coastguard Worker def __init__(self, binary_cache_dir: Optional[Union[Path, str]], readelf: ReadElf): 433*288bf522SAndroid Build Coastguard Worker if isinstance(binary_cache_dir, str): 434*288bf522SAndroid Build Coastguard Worker binary_cache_dir = Path(binary_cache_dir) 435*288bf522SAndroid Build Coastguard Worker self.binary_cache_dir = binary_cache_dir 436*288bf522SAndroid Build Coastguard Worker self.readelf = readelf 437*288bf522SAndroid Build Coastguard Worker self.build_id_map = self._load_build_id_map() 438*288bf522SAndroid Build Coastguard Worker 439*288bf522SAndroid Build Coastguard Worker def _load_build_id_map(self) -> Dict[str, Path]: 440*288bf522SAndroid Build Coastguard Worker build_id_map: Dict[str, Path] = {} 441*288bf522SAndroid Build Coastguard Worker if self.binary_cache_dir: 442*288bf522SAndroid Build Coastguard Worker build_id_list_file = self.binary_cache_dir / 'build_id_list' 443*288bf522SAndroid Build Coastguard Worker if build_id_list_file.is_file(): 444*288bf522SAndroid Build Coastguard Worker with open(self.binary_cache_dir / 'build_id_list', 'rb') as fh: 445*288bf522SAndroid Build Coastguard Worker for line in fh.readlines(): 446*288bf522SAndroid Build Coastguard Worker # lines are in format "<build_id>=<path_in_binary_cache>". 447*288bf522SAndroid Build Coastguard Worker items = bytes_to_str(line).strip().split('=') 448*288bf522SAndroid Build Coastguard Worker if len(items) == 2: 449*288bf522SAndroid Build Coastguard Worker build_id_map[items[0]] = self.binary_cache_dir / items[1] 450*288bf522SAndroid Build Coastguard Worker return build_id_map 451*288bf522SAndroid Build Coastguard Worker 452*288bf522SAndroid Build Coastguard Worker def find_binary(self, dso_path_in_record_file: str, 453*288bf522SAndroid Build Coastguard Worker expected_build_id: Optional[str]) -> Optional[Path]: 454*288bf522SAndroid Build Coastguard Worker """ If expected_build_id is None, don't check build id. 455*288bf522SAndroid Build Coastguard Worker Otherwise, the build id of the found binary should match the expected one.""" 456*288bf522SAndroid Build Coastguard Worker # Find binary from build id map. 457*288bf522SAndroid Build Coastguard Worker if expected_build_id: 458*288bf522SAndroid Build Coastguard Worker path = self.build_id_map.get(expected_build_id) 459*288bf522SAndroid Build Coastguard Worker if path and self._check_path(path, expected_build_id): 460*288bf522SAndroid Build Coastguard Worker return path 461*288bf522SAndroid Build Coastguard Worker # Find binary by path in binary cache. 462*288bf522SAndroid Build Coastguard Worker if self.binary_cache_dir: 463*288bf522SAndroid Build Coastguard Worker path = self.binary_cache_dir / dso_path_in_record_file[1:].replace('/', os.sep) 464*288bf522SAndroid Build Coastguard Worker if self._check_path(path, expected_build_id): 465*288bf522SAndroid Build Coastguard Worker return path 466*288bf522SAndroid Build Coastguard Worker # Find binary by its absolute path. 467*288bf522SAndroid Build Coastguard Worker path = Path(dso_path_in_record_file) 468*288bf522SAndroid Build Coastguard Worker if self._check_path(path, expected_build_id): 469*288bf522SAndroid Build Coastguard Worker return path 470*288bf522SAndroid Build Coastguard Worker return None 471*288bf522SAndroid Build Coastguard Worker 472*288bf522SAndroid Build Coastguard Worker def _check_path(self, path: Path, expected_build_id: Optional[str]) -> bool: 473*288bf522SAndroid Build Coastguard Worker if not self.readelf.is_elf_file(path): 474*288bf522SAndroid Build Coastguard Worker return False 475*288bf522SAndroid Build Coastguard Worker if expected_build_id is not None: 476*288bf522SAndroid Build Coastguard Worker return self.readelf.get_build_id(path) == expected_build_id 477*288bf522SAndroid Build Coastguard Worker return True 478*288bf522SAndroid Build Coastguard Worker 479*288bf522SAndroid Build Coastguard Worker 480*288bf522SAndroid Build Coastguard Workerclass Addr2Nearestline(object): 481*288bf522SAndroid Build Coastguard Worker """ Use llvm-symbolizer to convert (dso_path, func_addr, addr) to (source_file, line). 482*288bf522SAndroid Build Coastguard Worker For instructions generated by C++ compilers without a matching statement in source code 483*288bf522SAndroid Build Coastguard Worker (like stack corruption check, switch optimization, etc.), addr2line can't generate 484*288bf522SAndroid Build Coastguard Worker line information. However, we want to assign the instruction to the nearest line before 485*288bf522SAndroid Build Coastguard Worker the instruction (just like objdump -dl). So we use below strategy: 486*288bf522SAndroid Build Coastguard Worker Instead of finding the exact line of the instruction in an address, we find the nearest 487*288bf522SAndroid Build Coastguard Worker line to the instruction in an address. If an address doesn't have a line info, we find 488*288bf522SAndroid Build Coastguard Worker the line info of address - 1. If still no line info, then use address - 2, address - 3, 489*288bf522SAndroid Build Coastguard Worker etc. 490*288bf522SAndroid Build Coastguard Worker 491*288bf522SAndroid Build Coastguard Worker The implementation steps are as below: 492*288bf522SAndroid Build Coastguard Worker 1. Collect all (dso_path, func_addr, addr) requests before converting. This saves the 493*288bf522SAndroid Build Coastguard Worker times to call addr2line. 494*288bf522SAndroid Build Coastguard Worker 2. Convert addrs to (source_file, line) pairs for each dso_path as below: 495*288bf522SAndroid Build Coastguard Worker 2.1 Check if the dso_path has .debug_line. If not, omit its conversion. 496*288bf522SAndroid Build Coastguard Worker 2.2 Get arch of the dso_path, and decide the addr_step for it. addr_step is the step we 497*288bf522SAndroid Build Coastguard Worker change addr each time. For example, since instructions of arm64 are all 4 bytes long, 498*288bf522SAndroid Build Coastguard Worker addr_step for arm64 can be 4. 499*288bf522SAndroid Build Coastguard Worker 2.3 Use addr2line to find line info for each addr in the dso_path. 500*288bf522SAndroid Build Coastguard Worker 2.4 For each addr without line info, use addr2line to find line info for 501*288bf522SAndroid Build Coastguard Worker range(addr - addr_step, addr - addr_step * 4 - 1, -addr_step). 502*288bf522SAndroid Build Coastguard Worker 2.5 For each addr without line info, use addr2line to find line info for 503*288bf522SAndroid Build Coastguard Worker range(addr - addr_step * 5, addr - addr_step * 128 - 1, -addr_step). 504*288bf522SAndroid Build Coastguard Worker (128 is a guess number. A nested switch statement in 505*288bf522SAndroid Build Coastguard Worker system/core/demangle/Demangler.cpp has >300 bytes without line info in arm64.) 506*288bf522SAndroid Build Coastguard Worker """ 507*288bf522SAndroid Build Coastguard Worker class Dso(object): 508*288bf522SAndroid Build Coastguard Worker """ Info of a dynamic shared library. 509*288bf522SAndroid Build Coastguard Worker addrs: a map from address to Addr object in this dso. 510*288bf522SAndroid Build Coastguard Worker """ 511*288bf522SAndroid Build Coastguard Worker 512*288bf522SAndroid Build Coastguard Worker def __init__(self, build_id: Optional[str]): 513*288bf522SAndroid Build Coastguard Worker self.build_id = build_id 514*288bf522SAndroid Build Coastguard Worker self.addrs: Dict[int, Addr2Nearestline.Addr] = {} 515*288bf522SAndroid Build Coastguard Worker # Saving file names for each addr takes a lot of memory. So we store file ids in Addr, 516*288bf522SAndroid Build Coastguard Worker # and provide data structures connecting file id and file name here. 517*288bf522SAndroid Build Coastguard Worker self.file_name_to_id: Dict[str, int] = {} 518*288bf522SAndroid Build Coastguard Worker self.file_id_to_name: List[str] = [] 519*288bf522SAndroid Build Coastguard Worker self.func_name_to_id: Dict[str, int] = {} 520*288bf522SAndroid Build Coastguard Worker self.func_id_to_name: List[str] = [] 521*288bf522SAndroid Build Coastguard Worker 522*288bf522SAndroid Build Coastguard Worker def get_file_id(self, file_path: str) -> int: 523*288bf522SAndroid Build Coastguard Worker file_id = self.file_name_to_id.get(file_path) 524*288bf522SAndroid Build Coastguard Worker if file_id is None: 525*288bf522SAndroid Build Coastguard Worker file_id = self.file_name_to_id[file_path] = len(self.file_id_to_name) 526*288bf522SAndroid Build Coastguard Worker self.file_id_to_name.append(file_path) 527*288bf522SAndroid Build Coastguard Worker return file_id 528*288bf522SAndroid Build Coastguard Worker 529*288bf522SAndroid Build Coastguard Worker def get_func_id(self, func_name: str) -> int: 530*288bf522SAndroid Build Coastguard Worker func_id = self.func_name_to_id.get(func_name) 531*288bf522SAndroid Build Coastguard Worker if func_id is None: 532*288bf522SAndroid Build Coastguard Worker func_id = self.func_name_to_id[func_name] = len(self.func_id_to_name) 533*288bf522SAndroid Build Coastguard Worker self.func_id_to_name.append(func_name) 534*288bf522SAndroid Build Coastguard Worker return func_id 535*288bf522SAndroid Build Coastguard Worker 536*288bf522SAndroid Build Coastguard Worker class Addr(object): 537*288bf522SAndroid Build Coastguard Worker """ Info of an addr request. 538*288bf522SAndroid Build Coastguard Worker func_addr: start_addr of the function containing addr. 539*288bf522SAndroid Build Coastguard Worker source_lines: a list of [file_id, line_number] for addr. 540*288bf522SAndroid Build Coastguard Worker source_lines[:-1] are all for inlined functions. 541*288bf522SAndroid Build Coastguard Worker """ 542*288bf522SAndroid Build Coastguard Worker 543*288bf522SAndroid Build Coastguard Worker def __init__(self, func_addr: int): 544*288bf522SAndroid Build Coastguard Worker self.func_addr = func_addr 545*288bf522SAndroid Build Coastguard Worker self.source_lines: Optional[List[int, int]] = None 546*288bf522SAndroid Build Coastguard Worker 547*288bf522SAndroid Build Coastguard Worker def __init__( 548*288bf522SAndroid Build Coastguard Worker self, ndk_path: Optional[str], 549*288bf522SAndroid Build Coastguard Worker binary_finder: BinaryFinder, with_function_name: bool): 550*288bf522SAndroid Build Coastguard Worker self.symbolizer_path = ToolFinder.find_tool_path('llvm-symbolizer', ndk_path) 551*288bf522SAndroid Build Coastguard Worker if not self.symbolizer_path: 552*288bf522SAndroid Build Coastguard Worker log_exit("Can't find llvm-symbolizer. " + NDK_ERROR_MESSAGE) 553*288bf522SAndroid Build Coastguard Worker self.readelf = ReadElf(ndk_path) 554*288bf522SAndroid Build Coastguard Worker self.dso_map: Dict[str, Addr2Nearestline.Dso] = {} # map from dso_path to Dso. 555*288bf522SAndroid Build Coastguard Worker self.binary_finder = binary_finder 556*288bf522SAndroid Build Coastguard Worker self.with_function_name = with_function_name 557*288bf522SAndroid Build Coastguard Worker 558*288bf522SAndroid Build Coastguard Worker def add_addr(self, dso_path: str, build_id: Optional[str], func_addr: int, addr: int): 559*288bf522SAndroid Build Coastguard Worker dso = self.dso_map.get(dso_path) 560*288bf522SAndroid Build Coastguard Worker if dso is None: 561*288bf522SAndroid Build Coastguard Worker dso = self.dso_map[dso_path] = self.Dso(build_id) 562*288bf522SAndroid Build Coastguard Worker if addr not in dso.addrs: 563*288bf522SAndroid Build Coastguard Worker dso.addrs[addr] = self.Addr(func_addr) 564*288bf522SAndroid Build Coastguard Worker 565*288bf522SAndroid Build Coastguard Worker def convert_addrs_to_lines(self, jobs: int): 566*288bf522SAndroid Build Coastguard Worker with ThreadPoolExecutor(jobs) as executor: 567*288bf522SAndroid Build Coastguard Worker futures: List[Future] = [] 568*288bf522SAndroid Build Coastguard Worker for dso_path, dso in self.dso_map.items(): 569*288bf522SAndroid Build Coastguard Worker futures.append(executor.submit(self._convert_addrs_in_one_dso, dso_path, dso)) 570*288bf522SAndroid Build Coastguard Worker for future in futures: 571*288bf522SAndroid Build Coastguard Worker # Call future.result() to report exceptions raised in the executor. 572*288bf522SAndroid Build Coastguard Worker future.result() 573*288bf522SAndroid Build Coastguard Worker 574*288bf522SAndroid Build Coastguard Worker def _convert_addrs_in_one_dso(self, dso_path: str, dso: Addr2Nearestline.Dso): 575*288bf522SAndroid Build Coastguard Worker real_path = self.binary_finder.find_binary(dso_path, dso.build_id) 576*288bf522SAndroid Build Coastguard Worker if not real_path: 577*288bf522SAndroid Build Coastguard Worker if dso_path not in ['//anon', 'unknown', '[kernel.kallsyms]']: 578*288bf522SAndroid Build Coastguard Worker logging.debug("Can't find dso %s" % dso_path) 579*288bf522SAndroid Build Coastguard Worker return 580*288bf522SAndroid Build Coastguard Worker 581*288bf522SAndroid Build Coastguard Worker if not self._check_debug_line_section(real_path): 582*288bf522SAndroid Build Coastguard Worker logging.debug("file %s doesn't contain .debug_line section." % real_path) 583*288bf522SAndroid Build Coastguard Worker return 584*288bf522SAndroid Build Coastguard Worker 585*288bf522SAndroid Build Coastguard Worker addr_step = self._get_addr_step(real_path) 586*288bf522SAndroid Build Coastguard Worker self._collect_line_info(dso, real_path, [0]) 587*288bf522SAndroid Build Coastguard Worker self._collect_line_info(dso, real_path, range(-addr_step, -addr_step * 4 - 1, -addr_step)) 588*288bf522SAndroid Build Coastguard Worker self._collect_line_info(dso, real_path, 589*288bf522SAndroid Build Coastguard Worker range(-addr_step * 5, -addr_step * 128 - 1, -addr_step)) 590*288bf522SAndroid Build Coastguard Worker 591*288bf522SAndroid Build Coastguard Worker def _check_debug_line_section(self, real_path: Path) -> bool: 592*288bf522SAndroid Build Coastguard Worker return '.debug_line' in self.readelf.get_sections(real_path) 593*288bf522SAndroid Build Coastguard Worker 594*288bf522SAndroid Build Coastguard Worker def _get_addr_step(self, real_path: Path) -> int: 595*288bf522SAndroid Build Coastguard Worker arch = self.readelf.get_arch(real_path) 596*288bf522SAndroid Build Coastguard Worker if arch == 'arm64': 597*288bf522SAndroid Build Coastguard Worker return 4 598*288bf522SAndroid Build Coastguard Worker if arch == 'arm': 599*288bf522SAndroid Build Coastguard Worker return 2 600*288bf522SAndroid Build Coastguard Worker return 1 601*288bf522SAndroid Build Coastguard Worker 602*288bf522SAndroid Build Coastguard Worker def _collect_line_info( 603*288bf522SAndroid Build Coastguard Worker self, dso: Addr2Nearestline.Dso, real_path: Path, addr_shifts: List[int]): 604*288bf522SAndroid Build Coastguard Worker """ Use addr2line to get line info in a dso, with given addr shifts. """ 605*288bf522SAndroid Build Coastguard Worker # 1. Collect addrs to send to addr2line. 606*288bf522SAndroid Build Coastguard Worker addr_set: Set[int] = set() 607*288bf522SAndroid Build Coastguard Worker for addr in dso.addrs: 608*288bf522SAndroid Build Coastguard Worker addr_obj = dso.addrs[addr] 609*288bf522SAndroid Build Coastguard Worker if addr_obj.source_lines: # already has source line, no need to search. 610*288bf522SAndroid Build Coastguard Worker continue 611*288bf522SAndroid Build Coastguard Worker for shift in addr_shifts: 612*288bf522SAndroid Build Coastguard Worker # The addr after shift shouldn't change to another function. 613*288bf522SAndroid Build Coastguard Worker shifted_addr = max(addr + shift, addr_obj.func_addr) 614*288bf522SAndroid Build Coastguard Worker addr_set.add(shifted_addr) 615*288bf522SAndroid Build Coastguard Worker if shifted_addr == addr_obj.func_addr: 616*288bf522SAndroid Build Coastguard Worker break 617*288bf522SAndroid Build Coastguard Worker if not addr_set: 618*288bf522SAndroid Build Coastguard Worker return 619*288bf522SAndroid Build Coastguard Worker addr_request = '\n'.join(['0x%x' % addr for addr in sorted(addr_set)]) 620*288bf522SAndroid Build Coastguard Worker 621*288bf522SAndroid Build Coastguard Worker # 2. Use addr2line to collect line info. 622*288bf522SAndroid Build Coastguard Worker try: 623*288bf522SAndroid Build Coastguard Worker subproc = subprocess.Popen(self._build_symbolizer_args(real_path), 624*288bf522SAndroid Build Coastguard Worker stdin=subprocess.PIPE, stdout=subprocess.PIPE) 625*288bf522SAndroid Build Coastguard Worker (stdoutdata, _) = subproc.communicate(str_to_bytes(addr_request)) 626*288bf522SAndroid Build Coastguard Worker stdoutdata = bytes_to_str(stdoutdata) 627*288bf522SAndroid Build Coastguard Worker except OSError: 628*288bf522SAndroid Build Coastguard Worker return 629*288bf522SAndroid Build Coastguard Worker addr_map = self.parse_line_output(stdoutdata, dso) 630*288bf522SAndroid Build Coastguard Worker 631*288bf522SAndroid Build Coastguard Worker # 3. Fill line info in dso.addrs. 632*288bf522SAndroid Build Coastguard Worker for addr in dso.addrs: 633*288bf522SAndroid Build Coastguard Worker addr_obj = dso.addrs[addr] 634*288bf522SAndroid Build Coastguard Worker if addr_obj.source_lines: 635*288bf522SAndroid Build Coastguard Worker continue 636*288bf522SAndroid Build Coastguard Worker for shift in addr_shifts: 637*288bf522SAndroid Build Coastguard Worker shifted_addr = max(addr + shift, addr_obj.func_addr) 638*288bf522SAndroid Build Coastguard Worker lines = addr_map.get(shifted_addr) 639*288bf522SAndroid Build Coastguard Worker if lines: 640*288bf522SAndroid Build Coastguard Worker addr_obj.source_lines = lines 641*288bf522SAndroid Build Coastguard Worker break 642*288bf522SAndroid Build Coastguard Worker if shifted_addr == addr_obj.func_addr: 643*288bf522SAndroid Build Coastguard Worker break 644*288bf522SAndroid Build Coastguard Worker 645*288bf522SAndroid Build Coastguard Worker def _build_symbolizer_args(self, binary_path: Path) -> List[str]: 646*288bf522SAndroid Build Coastguard Worker args = [self.symbolizer_path, '--print-address', '--inlining', '--obj=%s' % binary_path] 647*288bf522SAndroid Build Coastguard Worker if self.with_function_name: 648*288bf522SAndroid Build Coastguard Worker args += ['--functions=linkage', '--demangle'] 649*288bf522SAndroid Build Coastguard Worker else: 650*288bf522SAndroid Build Coastguard Worker args.append('--functions=none') 651*288bf522SAndroid Build Coastguard Worker return args 652*288bf522SAndroid Build Coastguard Worker 653*288bf522SAndroid Build Coastguard Worker def parse_line_output(self, output: str, dso: Addr2Nearestline.Dso) -> Dict[int, 654*288bf522SAndroid Build Coastguard Worker List[Tuple[int]]]: 655*288bf522SAndroid Build Coastguard Worker """ 656*288bf522SAndroid Build Coastguard Worker The output is a list of lines. 657*288bf522SAndroid Build Coastguard Worker address1 658*288bf522SAndroid Build Coastguard Worker function_name1 (the function name can be empty) 659*288bf522SAndroid Build Coastguard Worker source_location1 660*288bf522SAndroid Build Coastguard Worker function_name2 661*288bf522SAndroid Build Coastguard Worker source_location2 662*288bf522SAndroid Build Coastguard Worker ... 663*288bf522SAndroid Build Coastguard Worker (end with empty line) 664*288bf522SAndroid Build Coastguard Worker """ 665*288bf522SAndroid Build Coastguard Worker 666*288bf522SAndroid Build Coastguard Worker addr_map: Dict[int, List[Tuple[int]]] = {} 667*288bf522SAndroid Build Coastguard Worker lines = output.strip().splitlines() 668*288bf522SAndroid Build Coastguard Worker i = 0 669*288bf522SAndroid Build Coastguard Worker while i < len(lines): 670*288bf522SAndroid Build Coastguard Worker address = self._parse_line_output_address(lines[i]) 671*288bf522SAndroid Build Coastguard Worker i += 1 672*288bf522SAndroid Build Coastguard Worker if address is None: 673*288bf522SAndroid Build Coastguard Worker continue 674*288bf522SAndroid Build Coastguard Worker info = [] 675*288bf522SAndroid Build Coastguard Worker while i < len(lines): 676*288bf522SAndroid Build Coastguard Worker if self.with_function_name: 677*288bf522SAndroid Build Coastguard Worker if i + 1 == len(lines): 678*288bf522SAndroid Build Coastguard Worker break 679*288bf522SAndroid Build Coastguard Worker function_name = lines[i].strip() 680*288bf522SAndroid Build Coastguard Worker if not function_name and (':' not in lines[i+1]): 681*288bf522SAndroid Build Coastguard Worker # no more frames 682*288bf522SAndroid Build Coastguard Worker break 683*288bf522SAndroid Build Coastguard Worker i += 1 684*288bf522SAndroid Build Coastguard Worker elif not lines[i]: 685*288bf522SAndroid Build Coastguard Worker i += 1 686*288bf522SAndroid Build Coastguard Worker break 687*288bf522SAndroid Build Coastguard Worker 688*288bf522SAndroid Build Coastguard Worker file_path, line_number = self._parse_line_output_source_location(lines[i]) 689*288bf522SAndroid Build Coastguard Worker i += 1 690*288bf522SAndroid Build Coastguard Worker if not file_path or not line_number: 691*288bf522SAndroid Build Coastguard Worker # An addr can have a list of (file, line), when the addr belongs to an inlined 692*288bf522SAndroid Build Coastguard Worker # function. Sometimes only part of the list has ? mark. In this case, we think 693*288bf522SAndroid Build Coastguard Worker # the line info is valid if the first line doesn't have ? mark. 694*288bf522SAndroid Build Coastguard Worker if not info: 695*288bf522SAndroid Build Coastguard Worker break 696*288bf522SAndroid Build Coastguard Worker continue 697*288bf522SAndroid Build Coastguard Worker file_id = dso.get_file_id(file_path) 698*288bf522SAndroid Build Coastguard Worker if self.with_function_name: 699*288bf522SAndroid Build Coastguard Worker func_id = dso.get_func_id(function_name) 700*288bf522SAndroid Build Coastguard Worker info.append((file_id, line_number, func_id)) 701*288bf522SAndroid Build Coastguard Worker else: 702*288bf522SAndroid Build Coastguard Worker info.append((file_id, line_number)) 703*288bf522SAndroid Build Coastguard Worker if info: 704*288bf522SAndroid Build Coastguard Worker addr_map[address] = info 705*288bf522SAndroid Build Coastguard Worker return addr_map 706*288bf522SAndroid Build Coastguard Worker 707*288bf522SAndroid Build Coastguard Worker def _parse_line_output_address(self, output: str) -> Optional[int]: 708*288bf522SAndroid Build Coastguard Worker if output.startswith('0x'): 709*288bf522SAndroid Build Coastguard Worker return int(output, 16) 710*288bf522SAndroid Build Coastguard Worker return None 711*288bf522SAndroid Build Coastguard Worker 712*288bf522SAndroid Build Coastguard Worker def _parse_line_output_source_location(self, line: str) -> Tuple[Optional[str], Optional[int]]: 713*288bf522SAndroid Build Coastguard Worker file_path, line_number = None, None 714*288bf522SAndroid Build Coastguard Worker # Handle lines in format filename:line:column, like "runtest/two_functions.cpp:14:25". 715*288bf522SAndroid Build Coastguard Worker # Filename may contain ':' like "C:\Users\...\file". 716*288bf522SAndroid Build Coastguard Worker items = line.rsplit(':', 2) 717*288bf522SAndroid Build Coastguard Worker if len(items) == 3: 718*288bf522SAndroid Build Coastguard Worker file_path, line_number = items[:2] 719*288bf522SAndroid Build Coastguard Worker if not file_path or ('?' in file_path) or not line_number or ('?' in line_number): 720*288bf522SAndroid Build Coastguard Worker return None, None 721*288bf522SAndroid Build Coastguard Worker try: 722*288bf522SAndroid Build Coastguard Worker line_number = int(line_number) 723*288bf522SAndroid Build Coastguard Worker except ValueError: 724*288bf522SAndroid Build Coastguard Worker return None, None 725*288bf522SAndroid Build Coastguard Worker return file_path, line_number 726*288bf522SAndroid Build Coastguard Worker 727*288bf522SAndroid Build Coastguard Worker def get_dso(self, dso_path: str) -> Addr2Nearestline.Dso: 728*288bf522SAndroid Build Coastguard Worker return self.dso_map.get(dso_path) 729*288bf522SAndroid Build Coastguard Worker 730*288bf522SAndroid Build Coastguard Worker def get_addr_source(self, dso: Addr2Nearestline.Dso, addr: int) -> Optional[List[Tuple[int]]]: 731*288bf522SAndroid Build Coastguard Worker source = dso.addrs[addr].source_lines 732*288bf522SAndroid Build Coastguard Worker if source is None: 733*288bf522SAndroid Build Coastguard Worker return None 734*288bf522SAndroid Build Coastguard Worker if self.with_function_name: 735*288bf522SAndroid Build Coastguard Worker return [(dso.file_id_to_name[file_id], line, dso.func_id_to_name[func_id]) 736*288bf522SAndroid Build Coastguard Worker for (file_id, line, func_id) in source] 737*288bf522SAndroid Build Coastguard Worker return [(dso.file_id_to_name[file_id], line) for (file_id, line) in source] 738*288bf522SAndroid Build Coastguard Worker 739*288bf522SAndroid Build Coastguard Worker 740*288bf522SAndroid Build Coastguard Workerclass SourceFileSearcher(object): 741*288bf522SAndroid Build Coastguard Worker """ Find source file paths in the file system. 742*288bf522SAndroid Build Coastguard Worker The file paths reported by addr2line are the paths stored in debug sections 743*288bf522SAndroid Build Coastguard Worker of shared libraries. And we need to convert them to file paths in the file 744*288bf522SAndroid Build Coastguard Worker system. It is done in below steps: 745*288bf522SAndroid Build Coastguard Worker 1. Collect all file paths under the provided source_dirs. The suffix of a 746*288bf522SAndroid Build Coastguard Worker source file should contain one of below: 747*288bf522SAndroid Build Coastguard Worker h: for C/C++ header files. 748*288bf522SAndroid Build Coastguard Worker c: for C/C++ source files. 749*288bf522SAndroid Build Coastguard Worker java: for Java source files. 750*288bf522SAndroid Build Coastguard Worker kt: for Kotlin source files. 751*288bf522SAndroid Build Coastguard Worker 2. Given an abstract_path reported by addr2line, select the best real path 752*288bf522SAndroid Build Coastguard Worker as below: 753*288bf522SAndroid Build Coastguard Worker 2.1 Find all real paths with the same file name as the abstract path. 754*288bf522SAndroid Build Coastguard Worker 2.2 Select the real path having the longest common suffix with the abstract path. 755*288bf522SAndroid Build Coastguard Worker """ 756*288bf522SAndroid Build Coastguard Worker 757*288bf522SAndroid Build Coastguard Worker SOURCE_FILE_EXTS = {'.h', '.hh', '.H', '.hxx', '.hpp', '.h++', 758*288bf522SAndroid Build Coastguard Worker '.c', '.cc', '.C', '.cxx', '.cpp', '.c++', 759*288bf522SAndroid Build Coastguard Worker '.java', '.kt'} 760*288bf522SAndroid Build Coastguard Worker 761*288bf522SAndroid Build Coastguard Worker @classmethod 762*288bf522SAndroid Build Coastguard Worker def is_source_filename(cls, filename: str) -> bool: 763*288bf522SAndroid Build Coastguard Worker ext = os.path.splitext(filename)[1] 764*288bf522SAndroid Build Coastguard Worker return ext in cls.SOURCE_FILE_EXTS 765*288bf522SAndroid Build Coastguard Worker 766*288bf522SAndroid Build Coastguard Worker def __init__(self, source_dirs: List[str]): 767*288bf522SAndroid Build Coastguard Worker # Map from filename to a list of reversed directory path containing filename. 768*288bf522SAndroid Build Coastguard Worker self.filename_to_rparents: Dict[str, List[str]] = {} 769*288bf522SAndroid Build Coastguard Worker self._collect_paths(source_dirs) 770*288bf522SAndroid Build Coastguard Worker 771*288bf522SAndroid Build Coastguard Worker def _collect_paths(self, source_dirs: List[str]): 772*288bf522SAndroid Build Coastguard Worker for source_dir in source_dirs: 773*288bf522SAndroid Build Coastguard Worker for parent, _, file_names in os.walk(source_dir): 774*288bf522SAndroid Build Coastguard Worker rparent = None 775*288bf522SAndroid Build Coastguard Worker for file_name in file_names: 776*288bf522SAndroid Build Coastguard Worker if self.is_source_filename(file_name): 777*288bf522SAndroid Build Coastguard Worker rparents = self.filename_to_rparents.get(file_name) 778*288bf522SAndroid Build Coastguard Worker if rparents is None: 779*288bf522SAndroid Build Coastguard Worker rparents = self.filename_to_rparents[file_name] = [] 780*288bf522SAndroid Build Coastguard Worker if rparent is None: 781*288bf522SAndroid Build Coastguard Worker rparent = parent[::-1] 782*288bf522SAndroid Build Coastguard Worker rparents.append(rparent) 783*288bf522SAndroid Build Coastguard Worker 784*288bf522SAndroid Build Coastguard Worker def get_real_path(self, abstract_path: str) -> Optional[str]: 785*288bf522SAndroid Build Coastguard Worker abstract_path = abstract_path.replace('/', os.sep) 786*288bf522SAndroid Build Coastguard Worker abstract_parent, file_name = os.path.split(abstract_path) 787*288bf522SAndroid Build Coastguard Worker abstract_rparent = abstract_parent[::-1] 788*288bf522SAndroid Build Coastguard Worker real_rparents = self.filename_to_rparents.get(file_name) 789*288bf522SAndroid Build Coastguard Worker if real_rparents is None: 790*288bf522SAndroid Build Coastguard Worker return None 791*288bf522SAndroid Build Coastguard Worker best_matched_rparent = None 792*288bf522SAndroid Build Coastguard Worker best_common_length = -1 793*288bf522SAndroid Build Coastguard Worker for real_rparent in real_rparents: 794*288bf522SAndroid Build Coastguard Worker length = len(os.path.commonprefix((real_rparent, abstract_rparent))) 795*288bf522SAndroid Build Coastguard Worker if length > best_common_length: 796*288bf522SAndroid Build Coastguard Worker best_common_length = length 797*288bf522SAndroid Build Coastguard Worker best_matched_rparent = real_rparent 798*288bf522SAndroid Build Coastguard Worker if best_matched_rparent is None: 799*288bf522SAndroid Build Coastguard Worker return None 800*288bf522SAndroid Build Coastguard Worker return os.path.join(best_matched_rparent[::-1], file_name) 801*288bf522SAndroid Build Coastguard Worker 802*288bf522SAndroid Build Coastguard Worker 803*288bf522SAndroid Build Coastguard Workerclass AddrRange: 804*288bf522SAndroid Build Coastguard Worker def __init__(self, start: int, len: int): 805*288bf522SAndroid Build Coastguard Worker self.start = start 806*288bf522SAndroid Build Coastguard Worker self.len = len 807*288bf522SAndroid Build Coastguard Worker 808*288bf522SAndroid Build Coastguard Worker @property 809*288bf522SAndroid Build Coastguard Worker def end(self) -> int: 810*288bf522SAndroid Build Coastguard Worker return self.start + self.len 811*288bf522SAndroid Build Coastguard Worker 812*288bf522SAndroid Build Coastguard Worker def is_in_range(self, addr: int) -> bool: 813*288bf522SAndroid Build Coastguard Worker return addr >= self.start and addr < self.end 814*288bf522SAndroid Build Coastguard Worker 815*288bf522SAndroid Build Coastguard Worker 816*288bf522SAndroid Build Coastguard Workerclass Disassembly: 817*288bf522SAndroid Build Coastguard Worker def __init__(self): 818*288bf522SAndroid Build Coastguard Worker self.lines: List[Tuple[str, int]] = [] 819*288bf522SAndroid Build Coastguard Worker 820*288bf522SAndroid Build Coastguard Worker 821*288bf522SAndroid Build Coastguard Workerclass Objdump(object): 822*288bf522SAndroid Build Coastguard Worker """ A wrapper of objdump to disassemble code. """ 823*288bf522SAndroid Build Coastguard Worker 824*288bf522SAndroid Build Coastguard Worker def __init__(self, ndk_path: Optional[str], binary_finder: BinaryFinder): 825*288bf522SAndroid Build Coastguard Worker self.ndk_path = ndk_path 826*288bf522SAndroid Build Coastguard Worker self.binary_finder = binary_finder 827*288bf522SAndroid Build Coastguard Worker self.readelf = ReadElf(ndk_path) 828*288bf522SAndroid Build Coastguard Worker self.objdump_paths: Dict[str, str] = {} 829*288bf522SAndroid Build Coastguard Worker 830*288bf522SAndroid Build Coastguard Worker def get_dso_info(self, dso_path: str, expected_build_id: Optional[str] 831*288bf522SAndroid Build Coastguard Worker ) -> Optional[Tuple[str, str]]: 832*288bf522SAndroid Build Coastguard Worker real_path = self.binary_finder.find_binary(dso_path, expected_build_id) 833*288bf522SAndroid Build Coastguard Worker if not real_path: 834*288bf522SAndroid Build Coastguard Worker return None 835*288bf522SAndroid Build Coastguard Worker arch = self.readelf.get_arch(real_path) 836*288bf522SAndroid Build Coastguard Worker if arch == 'unknown': 837*288bf522SAndroid Build Coastguard Worker return None 838*288bf522SAndroid Build Coastguard Worker return (str(real_path), arch) 839*288bf522SAndroid Build Coastguard Worker 840*288bf522SAndroid Build Coastguard Worker def disassemble_function(self, dso_info, addr_range: AddrRange) -> Optional[Disassembly]: 841*288bf522SAndroid Build Coastguard Worker """ Disassemble code for an addr range in a binary. 842*288bf522SAndroid Build Coastguard Worker """ 843*288bf522SAndroid Build Coastguard Worker real_path, arch = dso_info 844*288bf522SAndroid Build Coastguard Worker objdump_path = self.objdump_paths.get(arch) 845*288bf522SAndroid Build Coastguard Worker if not objdump_path: 846*288bf522SAndroid Build Coastguard Worker objdump_path = ToolFinder.find_tool_path('llvm-objdump', self.ndk_path, arch) 847*288bf522SAndroid Build Coastguard Worker if not objdump_path: 848*288bf522SAndroid Build Coastguard Worker log_exit("Can't find llvm-objdump." + NDK_ERROR_MESSAGE) 849*288bf522SAndroid Build Coastguard Worker self.objdump_paths[arch] = objdump_path 850*288bf522SAndroid Build Coastguard Worker 851*288bf522SAndroid Build Coastguard Worker # Run objdump. 852*288bf522SAndroid Build Coastguard Worker args = [objdump_path, '-dlC', '--no-show-raw-insn', 853*288bf522SAndroid Build Coastguard Worker '--start-address=0x%x' % addr_range.start, 854*288bf522SAndroid Build Coastguard Worker '--stop-address=0x%x' % (addr_range.end), 855*288bf522SAndroid Build Coastguard Worker real_path] 856*288bf522SAndroid Build Coastguard Worker if arch == 'arm' and 'llvm-objdump' in objdump_path: 857*288bf522SAndroid Build Coastguard Worker args += ['--print-imm-hex'] 858*288bf522SAndroid Build Coastguard Worker logging.debug('disassembling: %s', ' '.join(args)) 859*288bf522SAndroid Build Coastguard Worker try: 860*288bf522SAndroid Build Coastguard Worker subproc = subprocess.Popen(args, stdout=subprocess.PIPE) 861*288bf522SAndroid Build Coastguard Worker (stdoutdata, _) = subproc.communicate() 862*288bf522SAndroid Build Coastguard Worker stdoutdata = bytes_to_str(stdoutdata) 863*288bf522SAndroid Build Coastguard Worker except OSError: 864*288bf522SAndroid Build Coastguard Worker return None 865*288bf522SAndroid Build Coastguard Worker 866*288bf522SAndroid Build Coastguard Worker if not stdoutdata: 867*288bf522SAndroid Build Coastguard Worker return None 868*288bf522SAndroid Build Coastguard Worker result = Disassembly() 869*288bf522SAndroid Build Coastguard Worker for line in stdoutdata.split('\n'): 870*288bf522SAndroid Build Coastguard Worker line = line.rstrip() # Remove '\r' on Windows. 871*288bf522SAndroid Build Coastguard Worker items = line.split(':', 1) 872*288bf522SAndroid Build Coastguard Worker try: 873*288bf522SAndroid Build Coastguard Worker addr = int(items[0], 16) 874*288bf522SAndroid Build Coastguard Worker except ValueError: 875*288bf522SAndroid Build Coastguard Worker addr = 0 876*288bf522SAndroid Build Coastguard Worker result.lines.append((line, addr)) 877*288bf522SAndroid Build Coastguard Worker return result 878*288bf522SAndroid Build Coastguard Worker 879*288bf522SAndroid Build Coastguard Worker def disassemble_functions(self, dso_info, sorted_addr_ranges: List[AddrRange] 880*288bf522SAndroid Build Coastguard Worker ) -> Optional[List[Disassembly]]: 881*288bf522SAndroid Build Coastguard Worker """ Disassemble code for multiple addr ranges in a binary. sorted_addr_ranges should be 882*288bf522SAndroid Build Coastguard Worker sorted by addr_range.start. 883*288bf522SAndroid Build Coastguard Worker """ 884*288bf522SAndroid Build Coastguard Worker if not sorted_addr_ranges: 885*288bf522SAndroid Build Coastguard Worker return [] 886*288bf522SAndroid Build Coastguard Worker real_path, arch = dso_info 887*288bf522SAndroid Build Coastguard Worker objdump_path = self.objdump_paths.get(arch) 888*288bf522SAndroid Build Coastguard Worker if not objdump_path: 889*288bf522SAndroid Build Coastguard Worker objdump_path = ToolFinder.find_tool_path('llvm-objdump', self.ndk_path, arch) 890*288bf522SAndroid Build Coastguard Worker if not objdump_path: 891*288bf522SAndroid Build Coastguard Worker log_exit("Can't find llvm-objdump." + NDK_ERROR_MESSAGE) 892*288bf522SAndroid Build Coastguard Worker self.objdump_paths[arch] = objdump_path 893*288bf522SAndroid Build Coastguard Worker 894*288bf522SAndroid Build Coastguard Worker # Run objdump. 895*288bf522SAndroid Build Coastguard Worker start_addr = sorted_addr_ranges[0].start 896*288bf522SAndroid Build Coastguard Worker stop_addr = max(addr_range.end for addr_range in sorted_addr_ranges) 897*288bf522SAndroid Build Coastguard Worker args = [objdump_path, '-dlC', '--no-show-raw-insn', 898*288bf522SAndroid Build Coastguard Worker '--start-address=0x%x' % start_addr, 899*288bf522SAndroid Build Coastguard Worker '--stop-address=0x%x' % stop_addr, 900*288bf522SAndroid Build Coastguard Worker real_path] 901*288bf522SAndroid Build Coastguard Worker if arch == 'arm' and 'llvm-objdump' in objdump_path: 902*288bf522SAndroid Build Coastguard Worker args += ['--print-imm-hex'] 903*288bf522SAndroid Build Coastguard Worker try: 904*288bf522SAndroid Build Coastguard Worker proc = subprocess.Popen(args, stdout=subprocess.PIPE, text=True) 905*288bf522SAndroid Build Coastguard Worker result = self._parse_disassembly_for_functions(proc.stdout, sorted_addr_ranges) 906*288bf522SAndroid Build Coastguard Worker proc.wait() 907*288bf522SAndroid Build Coastguard Worker except OSError: 908*288bf522SAndroid Build Coastguard Worker return None 909*288bf522SAndroid Build Coastguard Worker return result 910*288bf522SAndroid Build Coastguard Worker 911*288bf522SAndroid Build Coastguard Worker def _parse_disassembly_for_functions(self, fh: TextIO, sorted_addr_ranges: List[AddrRange]) -> Optional[List[Disassembly]]: 912*288bf522SAndroid Build Coastguard Worker current_id = 0 913*288bf522SAndroid Build Coastguard Worker in_range = False 914*288bf522SAndroid Build Coastguard Worker result = [Disassembly() for _ in sorted_addr_ranges] 915*288bf522SAndroid Build Coastguard Worker while True: 916*288bf522SAndroid Build Coastguard Worker line = fh.readline() 917*288bf522SAndroid Build Coastguard Worker if not line: 918*288bf522SAndroid Build Coastguard Worker break 919*288bf522SAndroid Build Coastguard Worker line = line.rstrip() # Remove '\r\n'. 920*288bf522SAndroid Build Coastguard Worker addr = self._get_addr_from_disassembly_line(line) 921*288bf522SAndroid Build Coastguard Worker if current_id >= len(sorted_addr_ranges): 922*288bf522SAndroid Build Coastguard Worker continue 923*288bf522SAndroid Build Coastguard Worker if addr: 924*288bf522SAndroid Build Coastguard Worker if in_range and not sorted_addr_ranges[current_id].is_in_range(addr): 925*288bf522SAndroid Build Coastguard Worker in_range = False 926*288bf522SAndroid Build Coastguard Worker if not in_range: 927*288bf522SAndroid Build Coastguard Worker # Skip addr ranges before the current address. 928*288bf522SAndroid Build Coastguard Worker while current_id < len(sorted_addr_ranges) and sorted_addr_ranges[current_id].end <= addr: 929*288bf522SAndroid Build Coastguard Worker current_id += 1 930*288bf522SAndroid Build Coastguard Worker if current_id < len(sorted_addr_ranges) and sorted_addr_ranges[current_id].is_in_range(addr): 931*288bf522SAndroid Build Coastguard Worker in_range = True 932*288bf522SAndroid Build Coastguard Worker if in_range: 933*288bf522SAndroid Build Coastguard Worker result[current_id].lines.append((line, addr)) 934*288bf522SAndroid Build Coastguard Worker return result 935*288bf522SAndroid Build Coastguard Worker 936*288bf522SAndroid Build Coastguard Worker def _get_addr_from_disassembly_line(self, line: str) -> int: 937*288bf522SAndroid Build Coastguard Worker # line may be an instruction, like: " 24a469c: stp x29, x30, [sp, #-0x60]!" or 938*288bf522SAndroid Build Coastguard Worker # "ffffffc0085d9664: paciasp". 939*288bf522SAndroid Build Coastguard Worker # line may be a function start point, like "00000000024a4698 <DoWork()>:". 940*288bf522SAndroid Build Coastguard Worker items = line.strip().split() 941*288bf522SAndroid Build Coastguard Worker if not items: 942*288bf522SAndroid Build Coastguard Worker return 0 943*288bf522SAndroid Build Coastguard Worker s = items[0] 944*288bf522SAndroid Build Coastguard Worker if s.endswith(':'): 945*288bf522SAndroid Build Coastguard Worker s = s[:-1] 946*288bf522SAndroid Build Coastguard Worker try: 947*288bf522SAndroid Build Coastguard Worker return int(s, 16) 948*288bf522SAndroid Build Coastguard Worker except ValueError: 949*288bf522SAndroid Build Coastguard Worker return 0 950*288bf522SAndroid Build Coastguard Worker 951*288bf522SAndroid Build Coastguard Worker 952*288bf522SAndroid Build Coastguard Workerclass ReadElf(object): 953*288bf522SAndroid Build Coastguard Worker """ A wrapper of readelf. """ 954*288bf522SAndroid Build Coastguard Worker 955*288bf522SAndroid Build Coastguard Worker def __init__(self, ndk_path: Optional[str]): 956*288bf522SAndroid Build Coastguard Worker self.readelf_path = ToolFinder.find_tool_path('llvm-readelf', ndk_path) 957*288bf522SAndroid Build Coastguard Worker if not self.readelf_path: 958*288bf522SAndroid Build Coastguard Worker log_exit("Can't find llvm-readelf. " + NDK_ERROR_MESSAGE) 959*288bf522SAndroid Build Coastguard Worker 960*288bf522SAndroid Build Coastguard Worker @staticmethod 961*288bf522SAndroid Build Coastguard Worker def is_elf_file(path: Union[Path, str]) -> bool: 962*288bf522SAndroid Build Coastguard Worker if os.path.isfile(path): 963*288bf522SAndroid Build Coastguard Worker with open(path, 'rb') as fh: 964*288bf522SAndroid Build Coastguard Worker return fh.read(4) == b'\x7fELF' 965*288bf522SAndroid Build Coastguard Worker return False 966*288bf522SAndroid Build Coastguard Worker 967*288bf522SAndroid Build Coastguard Worker def get_arch(self, elf_file_path: Union[Path, str]) -> str: 968*288bf522SAndroid Build Coastguard Worker """ Get arch of an elf file. """ 969*288bf522SAndroid Build Coastguard Worker if self.is_elf_file(elf_file_path): 970*288bf522SAndroid Build Coastguard Worker try: 971*288bf522SAndroid Build Coastguard Worker output = subprocess.check_output([self.readelf_path, '-h', str(elf_file_path)]) 972*288bf522SAndroid Build Coastguard Worker output = bytes_to_str(output) 973*288bf522SAndroid Build Coastguard Worker if output.find('AArch64') != -1: 974*288bf522SAndroid Build Coastguard Worker return 'arm64' 975*288bf522SAndroid Build Coastguard Worker if output.find('ARM') != -1: 976*288bf522SAndroid Build Coastguard Worker return 'arm' 977*288bf522SAndroid Build Coastguard Worker if output.find('X86-64') != -1: 978*288bf522SAndroid Build Coastguard Worker return 'x86_64' 979*288bf522SAndroid Build Coastguard Worker if output.find('80386') != -1: 980*288bf522SAndroid Build Coastguard Worker return 'x86' 981*288bf522SAndroid Build Coastguard Worker if output.find('RISC-V') != -1: 982*288bf522SAndroid Build Coastguard Worker return 'riscv64' 983*288bf522SAndroid Build Coastguard Worker except subprocess.CalledProcessError: 984*288bf522SAndroid Build Coastguard Worker pass 985*288bf522SAndroid Build Coastguard Worker return 'unknown' 986*288bf522SAndroid Build Coastguard Worker 987*288bf522SAndroid Build Coastguard Worker def get_build_id(self, elf_file_path: Union[Path, str], with_padding=True) -> str: 988*288bf522SAndroid Build Coastguard Worker """ Get build id of an elf file. """ 989*288bf522SAndroid Build Coastguard Worker if self.is_elf_file(elf_file_path): 990*288bf522SAndroid Build Coastguard Worker try: 991*288bf522SAndroid Build Coastguard Worker output = subprocess.check_output([self.readelf_path, '-n', str(elf_file_path)]) 992*288bf522SAndroid Build Coastguard Worker output = bytes_to_str(output) 993*288bf522SAndroid Build Coastguard Worker result = re.search(r'Build ID:\s*(\S+)', output) 994*288bf522SAndroid Build Coastguard Worker if result: 995*288bf522SAndroid Build Coastguard Worker build_id = result.group(1) 996*288bf522SAndroid Build Coastguard Worker if with_padding: 997*288bf522SAndroid Build Coastguard Worker build_id = self.pad_build_id(build_id) 998*288bf522SAndroid Build Coastguard Worker return build_id 999*288bf522SAndroid Build Coastguard Worker except subprocess.CalledProcessError: 1000*288bf522SAndroid Build Coastguard Worker pass 1001*288bf522SAndroid Build Coastguard Worker return "" 1002*288bf522SAndroid Build Coastguard Worker 1003*288bf522SAndroid Build Coastguard Worker @staticmethod 1004*288bf522SAndroid Build Coastguard Worker def pad_build_id(build_id: str) -> str: 1005*288bf522SAndroid Build Coastguard Worker """ Pad build id to 40 hex numbers (20 bytes). """ 1006*288bf522SAndroid Build Coastguard Worker if len(build_id) < 40: 1007*288bf522SAndroid Build Coastguard Worker build_id += '0' * (40 - len(build_id)) 1008*288bf522SAndroid Build Coastguard Worker else: 1009*288bf522SAndroid Build Coastguard Worker build_id = build_id[:40] 1010*288bf522SAndroid Build Coastguard Worker return '0x' + build_id 1011*288bf522SAndroid Build Coastguard Worker 1012*288bf522SAndroid Build Coastguard Worker @staticmethod 1013*288bf522SAndroid Build Coastguard Worker def unpad_build_id(build_id: str) -> str: 1014*288bf522SAndroid Build Coastguard Worker if build_id.startswith('0x'): 1015*288bf522SAndroid Build Coastguard Worker build_id = build_id[2:] 1016*288bf522SAndroid Build Coastguard Worker # Unpad build id as TrimZeroesFromBuildIDString() in quipper. 1017*288bf522SAndroid Build Coastguard Worker padding = '0' * 8 1018*288bf522SAndroid Build Coastguard Worker while build_id.endswith(padding): 1019*288bf522SAndroid Build Coastguard Worker build_id = build_id[:-len(padding)] 1020*288bf522SAndroid Build Coastguard Worker return build_id 1021*288bf522SAndroid Build Coastguard Worker 1022*288bf522SAndroid Build Coastguard Worker def get_sections(self, elf_file_path: Union[Path, str]) -> List[str]: 1023*288bf522SAndroid Build Coastguard Worker """ Get sections of an elf file. """ 1024*288bf522SAndroid Build Coastguard Worker section_names: List[str] = [] 1025*288bf522SAndroid Build Coastguard Worker if self.is_elf_file(elf_file_path): 1026*288bf522SAndroid Build Coastguard Worker try: 1027*288bf522SAndroid Build Coastguard Worker output = subprocess.check_output([self.readelf_path, '-SW', str(elf_file_path)]) 1028*288bf522SAndroid Build Coastguard Worker output = bytes_to_str(output) 1029*288bf522SAndroid Build Coastguard Worker for line in output.split('\n'): 1030*288bf522SAndroid Build Coastguard Worker # Parse line like:" [ 1] .note.android.ident NOTE 0000000000400190 ...". 1031*288bf522SAndroid Build Coastguard Worker result = re.search(r'^\s+\[\s*\d+\]\s(.+?)\s', line) 1032*288bf522SAndroid Build Coastguard Worker if result: 1033*288bf522SAndroid Build Coastguard Worker section_name = result.group(1).strip() 1034*288bf522SAndroid Build Coastguard Worker if section_name: 1035*288bf522SAndroid Build Coastguard Worker section_names.append(section_name) 1036*288bf522SAndroid Build Coastguard Worker except subprocess.CalledProcessError: 1037*288bf522SAndroid Build Coastguard Worker pass 1038*288bf522SAndroid Build Coastguard Worker return section_names 1039*288bf522SAndroid Build Coastguard Worker 1040*288bf522SAndroid Build Coastguard Worker 1041*288bf522SAndroid Build Coastguard Workerdef extant_dir(arg: str) -> str: 1042*288bf522SAndroid Build Coastguard Worker """ArgumentParser type that only accepts extant directories. 1043*288bf522SAndroid Build Coastguard Worker 1044*288bf522SAndroid Build Coastguard Worker Args: 1045*288bf522SAndroid Build Coastguard Worker arg: The string argument given on the command line. 1046*288bf522SAndroid Build Coastguard Worker Returns: The argument as a realpath. 1047*288bf522SAndroid Build Coastguard Worker Raises: 1048*288bf522SAndroid Build Coastguard Worker argparse.ArgumentTypeError: The given path isn't a directory. 1049*288bf522SAndroid Build Coastguard Worker """ 1050*288bf522SAndroid Build Coastguard Worker path = os.path.realpath(arg) 1051*288bf522SAndroid Build Coastguard Worker if not os.path.isdir(path): 1052*288bf522SAndroid Build Coastguard Worker raise argparse.ArgumentTypeError('{} is not a directory.'.format(path)) 1053*288bf522SAndroid Build Coastguard Worker return path 1054*288bf522SAndroid Build Coastguard Worker 1055*288bf522SAndroid Build Coastguard Worker 1056*288bf522SAndroid Build Coastguard Workerdef extant_file(arg: str) -> str: 1057*288bf522SAndroid Build Coastguard Worker """ArgumentParser type that only accepts extant files. 1058*288bf522SAndroid Build Coastguard Worker 1059*288bf522SAndroid Build Coastguard Worker Args: 1060*288bf522SAndroid Build Coastguard Worker arg: The string argument given on the command line. 1061*288bf522SAndroid Build Coastguard Worker Returns: The argument as a realpath. 1062*288bf522SAndroid Build Coastguard Worker Raises: 1063*288bf522SAndroid Build Coastguard Worker argparse.ArgumentTypeError: The given path isn't a file. 1064*288bf522SAndroid Build Coastguard Worker """ 1065*288bf522SAndroid Build Coastguard Worker path = os.path.realpath(arg) 1066*288bf522SAndroid Build Coastguard Worker if not os.path.isfile(path): 1067*288bf522SAndroid Build Coastguard Worker raise argparse.ArgumentTypeError('{} is not a file.'.format(path)) 1068*288bf522SAndroid Build Coastguard Worker return path 1069*288bf522SAndroid Build Coastguard Worker 1070*288bf522SAndroid Build Coastguard Worker 1071*288bf522SAndroid Build Coastguard Workerdef log_fatal(msg: str): 1072*288bf522SAndroid Build Coastguard Worker raise Exception(msg) 1073*288bf522SAndroid Build Coastguard Worker 1074*288bf522SAndroid Build Coastguard Worker 1075*288bf522SAndroid Build Coastguard Workerdef log_exit(msg: str): 1076*288bf522SAndroid Build Coastguard Worker sys.exit(msg) 1077*288bf522SAndroid Build Coastguard Worker 1078*288bf522SAndroid Build Coastguard Worker 1079*288bf522SAndroid Build Coastguard Workerclass LogFormatter(logging.Formatter): 1080*288bf522SAndroid Build Coastguard Worker """ Use custom logging format. """ 1081*288bf522SAndroid Build Coastguard Worker 1082*288bf522SAndroid Build Coastguard Worker def __init__(self): 1083*288bf522SAndroid Build Coastguard Worker super().__init__('%(asctime)s [%(levelname)s] (%(filename)s:%(lineno)d) %(message)s') 1084*288bf522SAndroid Build Coastguard Worker 1085*288bf522SAndroid Build Coastguard Worker def formatTime(self, record, datefmt): 1086*288bf522SAndroid Build Coastguard Worker return super().formatTime(record, '%H:%M:%S') + ',%03d' % record.msecs 1087*288bf522SAndroid Build Coastguard Worker 1088*288bf522SAndroid Build Coastguard Worker 1089*288bf522SAndroid Build Coastguard Workerclass Log: 1090*288bf522SAndroid Build Coastguard Worker initialized = False 1091*288bf522SAndroid Build Coastguard Worker 1092*288bf522SAndroid Build Coastguard Worker @classmethod 1093*288bf522SAndroid Build Coastguard Worker def init(cls, log_level: str = 'info'): 1094*288bf522SAndroid Build Coastguard Worker assert not cls.initialized 1095*288bf522SAndroid Build Coastguard Worker cls.initialized = True 1096*288bf522SAndroid Build Coastguard Worker cls.logger = logging.root 1097*288bf522SAndroid Build Coastguard Worker cls.logger.setLevel(log_level.upper()) 1098*288bf522SAndroid Build Coastguard Worker handler = logging.StreamHandler() 1099*288bf522SAndroid Build Coastguard Worker handler.setFormatter(LogFormatter()) 1100*288bf522SAndroid Build Coastguard Worker cls.logger.addHandler(handler) 1101*288bf522SAndroid Build Coastguard Worker 1102*288bf522SAndroid Build Coastguard Worker 1103*288bf522SAndroid Build Coastguard Workerclass ArgParseFormatter( 1104*288bf522SAndroid Build Coastguard Worker argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): 1105*288bf522SAndroid Build Coastguard Worker pass 1106*288bf522SAndroid Build Coastguard Worker 1107*288bf522SAndroid Build Coastguard Worker 1108*288bf522SAndroid Build Coastguard Worker@dataclass 1109*288bf522SAndroid Build Coastguard Workerclass ReportLibOptions: 1110*288bf522SAndroid Build Coastguard Worker show_art_frames: bool 1111*288bf522SAndroid Build Coastguard Worker remove_method: List[str] 1112*288bf522SAndroid Build Coastguard Worker trace_offcpu: str 1113*288bf522SAndroid Build Coastguard Worker proguard_mapping_files: List[str] 1114*288bf522SAndroid Build Coastguard Worker sample_filters: List[str] 1115*288bf522SAndroid Build Coastguard Worker aggregate_threads: List[str] 1116*288bf522SAndroid Build Coastguard Worker 1117*288bf522SAndroid Build Coastguard Worker 1118*288bf522SAndroid Build Coastguard Workerclass BaseArgumentParser(argparse.ArgumentParser): 1119*288bf522SAndroid Build Coastguard Worker def __init__(self, *args, **kwargs): 1120*288bf522SAndroid Build Coastguard Worker super().__init__(*args, **kwargs, formatter_class=ArgParseFormatter) 1121*288bf522SAndroid Build Coastguard Worker self.has_sample_filter_options = False 1122*288bf522SAndroid Build Coastguard Worker self.sample_filter_with_pid_shortcut = False 1123*288bf522SAndroid Build Coastguard Worker self.has_report_lib_options = False 1124*288bf522SAndroid Build Coastguard Worker 1125*288bf522SAndroid Build Coastguard Worker def add_report_lib_options(self, group: Optional[Any] = None, 1126*288bf522SAndroid Build Coastguard Worker default_show_art_frames: bool = False, 1127*288bf522SAndroid Build Coastguard Worker sample_filter_group: Optional[Any] = None, 1128*288bf522SAndroid Build Coastguard Worker sample_filter_with_pid_shortcut: bool = True): 1129*288bf522SAndroid Build Coastguard Worker self.has_report_lib_options = True 1130*288bf522SAndroid Build Coastguard Worker parser = group if group else self 1131*288bf522SAndroid Build Coastguard Worker parser.add_argument( 1132*288bf522SAndroid Build Coastguard Worker '--proguard-mapping-file', nargs='+', 1133*288bf522SAndroid Build Coastguard Worker help='Add proguard mapping file to de-obfuscate symbols') 1134*288bf522SAndroid Build Coastguard Worker parser.add_argument('--show-art-frames', '--show_art_frames', 1135*288bf522SAndroid Build Coastguard Worker action=argparse.BooleanOptionalAction, default=default_show_art_frames, 1136*288bf522SAndroid Build Coastguard Worker help='Show frames of internal methods in the ART Java interpreter.') 1137*288bf522SAndroid Build Coastguard Worker parser.add_argument('--remove-method', nargs='+', metavar='method_name_regex', 1138*288bf522SAndroid Build Coastguard Worker help='remove methods with name containing the regular expression') 1139*288bf522SAndroid Build Coastguard Worker parser.add_argument( 1140*288bf522SAndroid Build Coastguard Worker '--trace-offcpu', choices=['on-cpu', 'off-cpu', 'on-off-cpu', 'mixed-on-off-cpu'], 1141*288bf522SAndroid Build Coastguard Worker help="""Set report mode for profiles recorded with --trace-offcpu option. All possible 1142*288bf522SAndroid Build Coastguard Worker modes are: on-cpu (only on-cpu samples), off-cpu (only off-cpu samples), 1143*288bf522SAndroid Build Coastguard Worker on-off-cpu (both on-cpu and off-cpu samples, can be split by event name), 1144*288bf522SAndroid Build Coastguard Worker mixed-on-off-cpu (on-cpu and off-cpu samples using the same event name). 1145*288bf522SAndroid Build Coastguard Worker If not set, mixed-on-off-cpu mode is used. 1146*288bf522SAndroid Build Coastguard Worker """) 1147*288bf522SAndroid Build Coastguard Worker self._add_sample_filter_options(sample_filter_group, sample_filter_with_pid_shortcut) 1148*288bf522SAndroid Build Coastguard Worker parser.add_argument( 1149*288bf522SAndroid Build Coastguard Worker '--aggregate-threads', nargs='+', metavar='thread_name_regex', 1150*288bf522SAndroid Build Coastguard Worker help="""Aggregate threads with names matching the same regex. As a result, samples from 1151*288bf522SAndroid Build Coastguard Worker different threads (like a thread pool) can be shown in one flamegraph. 1152*288bf522SAndroid Build Coastguard Worker """) 1153*288bf522SAndroid Build Coastguard Worker 1154*288bf522SAndroid Build Coastguard Worker def _add_sample_filter_options( 1155*288bf522SAndroid Build Coastguard Worker self, group: Optional[Any] = None, with_pid_shortcut: bool = True): 1156*288bf522SAndroid Build Coastguard Worker if not group: 1157*288bf522SAndroid Build Coastguard Worker group = self.add_argument_group('Sample filter options') 1158*288bf522SAndroid Build Coastguard Worker group.add_argument('--cpu', nargs='+', help="""only include samples for the selected cpus. 1159*288bf522SAndroid Build Coastguard Worker cpu can be a number like 1, or a range like 0-3""") 1160*288bf522SAndroid Build Coastguard Worker group.add_argument('--exclude-pid', metavar='pid', nargs='+', type=int, 1161*288bf522SAndroid Build Coastguard Worker help='exclude samples for selected processes') 1162*288bf522SAndroid Build Coastguard Worker group.add_argument('--exclude-tid', metavar='tid', nargs='+', type=int, 1163*288bf522SAndroid Build Coastguard Worker help='exclude samples for selected threads') 1164*288bf522SAndroid Build Coastguard Worker group.add_argument( 1165*288bf522SAndroid Build Coastguard Worker '--exclude-process-name', metavar='process_name_regex', nargs='+', 1166*288bf522SAndroid Build Coastguard Worker help='exclude samples for processes with name containing the regular expression') 1167*288bf522SAndroid Build Coastguard Worker group.add_argument( 1168*288bf522SAndroid Build Coastguard Worker '--exclude-thread-name', metavar='thread_name_regex', nargs='+', 1169*288bf522SAndroid Build Coastguard Worker help='exclude samples for threads with name containing the regular expression') 1170*288bf522SAndroid Build Coastguard Worker 1171*288bf522SAndroid Build Coastguard Worker if with_pid_shortcut: 1172*288bf522SAndroid Build Coastguard Worker group.add_argument('--pid', metavar='pid', nargs='+', type=int, 1173*288bf522SAndroid Build Coastguard Worker help='only include samples for selected processes') 1174*288bf522SAndroid Build Coastguard Worker group.add_argument('--tid', metavar='tid', nargs='+', type=int, 1175*288bf522SAndroid Build Coastguard Worker help='only include samples for selected threads') 1176*288bf522SAndroid Build Coastguard Worker group.add_argument('--include-pid', metavar='pid', nargs='+', type=int, 1177*288bf522SAndroid Build Coastguard Worker help='only include samples for selected processes') 1178*288bf522SAndroid Build Coastguard Worker group.add_argument('--include-tid', metavar='tid', nargs='+', type=int, 1179*288bf522SAndroid Build Coastguard Worker help='only include samples for selected threads') 1180*288bf522SAndroid Build Coastguard Worker group.add_argument( 1181*288bf522SAndroid Build Coastguard Worker '--include-process-name', metavar='process_name_regex', nargs='+', 1182*288bf522SAndroid Build Coastguard Worker help='only include samples for processes with name containing the regular expression') 1183*288bf522SAndroid Build Coastguard Worker group.add_argument( 1184*288bf522SAndroid Build Coastguard Worker '--comm', '--include-thread-name', metavar='thread_name_regex', 1185*288bf522SAndroid Build Coastguard Worker dest='include_thread_name', nargs='+', 1186*288bf522SAndroid Build Coastguard Worker help='only include samples for threads with name containing the regular expression') 1187*288bf522SAndroid Build Coastguard Worker group.add_argument( 1188*288bf522SAndroid Build Coastguard Worker '--filter-file', metavar='file', 1189*288bf522SAndroid Build Coastguard Worker help='use filter file to filter samples based on timestamps. ' + 1190*288bf522SAndroid Build Coastguard Worker 'The file format is in doc/sampler_filter.md.') 1191*288bf522SAndroid Build Coastguard Worker self.has_sample_filter_options = True 1192*288bf522SAndroid Build Coastguard Worker self.sample_filter_with_pid_shortcut = with_pid_shortcut 1193*288bf522SAndroid Build Coastguard Worker 1194*288bf522SAndroid Build Coastguard Worker def _build_sample_filter(self, args: argparse.Namespace) -> List[str]: 1195*288bf522SAndroid Build Coastguard Worker """ Build sample filters, which can be passed to ReportLib.SetSampleFilter(). """ 1196*288bf522SAndroid Build Coastguard Worker filters = [] 1197*288bf522SAndroid Build Coastguard Worker if args.cpu: 1198*288bf522SAndroid Build Coastguard Worker filters.extend(['--cpu', ','.join(args.cpu)]) 1199*288bf522SAndroid Build Coastguard Worker if args.exclude_pid: 1200*288bf522SAndroid Build Coastguard Worker filters.extend(['--exclude-pid', ','.join(str(pid) for pid in args.exclude_pid)]) 1201*288bf522SAndroid Build Coastguard Worker if args.exclude_tid: 1202*288bf522SAndroid Build Coastguard Worker filters.extend(['--exclude-tid', ','.join(str(tid) for tid in args.exclude_tid)]) 1203*288bf522SAndroid Build Coastguard Worker if args.exclude_process_name: 1204*288bf522SAndroid Build Coastguard Worker for name in args.exclude_process_name: 1205*288bf522SAndroid Build Coastguard Worker filters.extend(['--exclude-process-name', name]) 1206*288bf522SAndroid Build Coastguard Worker if args.exclude_thread_name: 1207*288bf522SAndroid Build Coastguard Worker for name in args.exclude_thread_name: 1208*288bf522SAndroid Build Coastguard Worker filters.extend(['--exclude-thread-name', name]) 1209*288bf522SAndroid Build Coastguard Worker 1210*288bf522SAndroid Build Coastguard Worker if args.include_pid: 1211*288bf522SAndroid Build Coastguard Worker filters.extend(['--include-pid', ','.join(str(pid) for pid in args.include_pid)]) 1212*288bf522SAndroid Build Coastguard Worker if args.include_tid: 1213*288bf522SAndroid Build Coastguard Worker filters.extend(['--include-tid', ','.join(str(tid) for tid in args.include_tid)]) 1214*288bf522SAndroid Build Coastguard Worker if self.sample_filter_with_pid_shortcut: 1215*288bf522SAndroid Build Coastguard Worker if args.pid: 1216*288bf522SAndroid Build Coastguard Worker filters.extend(['--include-pid', ','.join(str(pid) for pid in args.pid)]) 1217*288bf522SAndroid Build Coastguard Worker if args.tid: 1218*288bf522SAndroid Build Coastguard Worker filters.extend(['--include-tid', ','.join(str(pid) for pid in args.tid)]) 1219*288bf522SAndroid Build Coastguard Worker if args.include_process_name: 1220*288bf522SAndroid Build Coastguard Worker for name in args.include_process_name: 1221*288bf522SAndroid Build Coastguard Worker filters.extend(['--include-process-name', name]) 1222*288bf522SAndroid Build Coastguard Worker if args.include_thread_name: 1223*288bf522SAndroid Build Coastguard Worker for name in args.include_thread_name: 1224*288bf522SAndroid Build Coastguard Worker filters.extend(['--include-thread-name', name]) 1225*288bf522SAndroid Build Coastguard Worker if args.filter_file: 1226*288bf522SAndroid Build Coastguard Worker filters.extend(['--filter-file', args.filter_file]) 1227*288bf522SAndroid Build Coastguard Worker return filters 1228*288bf522SAndroid Build Coastguard Worker 1229*288bf522SAndroid Build Coastguard Worker def parse_known_args(self, *args, **kwargs): 1230*288bf522SAndroid Build Coastguard Worker self.add_argument( 1231*288bf522SAndroid Build Coastguard Worker '--log', choices=['debug', 'info', 'warning'], 1232*288bf522SAndroid Build Coastguard Worker default='info', help='set log level') 1233*288bf522SAndroid Build Coastguard Worker namespace, left_args = super().parse_known_args(*args, **kwargs) 1234*288bf522SAndroid Build Coastguard Worker 1235*288bf522SAndroid Build Coastguard Worker if self.has_report_lib_options: 1236*288bf522SAndroid Build Coastguard Worker sample_filters = self._build_sample_filter(namespace) 1237*288bf522SAndroid Build Coastguard Worker report_lib_options = ReportLibOptions( 1238*288bf522SAndroid Build Coastguard Worker namespace.show_art_frames, namespace.remove_method, namespace.trace_offcpu, 1239*288bf522SAndroid Build Coastguard Worker namespace.proguard_mapping_file, sample_filters, namespace.aggregate_threads) 1240*288bf522SAndroid Build Coastguard Worker setattr(namespace, 'report_lib_options', report_lib_options) 1241*288bf522SAndroid Build Coastguard Worker 1242*288bf522SAndroid Build Coastguard Worker if not Log.initialized: 1243*288bf522SAndroid Build Coastguard Worker Log.init(namespace.log) 1244*288bf522SAndroid Build Coastguard Worker return namespace, left_args 1245