xref: /aosp_15_r20/system/extras/simpleperf/scripts/simpleperf_utils.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
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