You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					685 lines
				
				24 KiB
			
		
		
			
		
	
	
					685 lines
				
				24 KiB
			| 
								 
											6 years ago
										 
									 | 
							
								#!/usr/bin/env python3
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Copyright (C) 2016 The Android Open Source Project
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Licensed under the Apache License, Version 2.0 (the "License");
							 | 
						||
| 
								 | 
							
								# you may not use this file except in compliance with the License.
							 | 
						||
| 
								 | 
							
								# You may obtain a copy of the License at
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#      http://www.apache.org/licenses/LICENSE-2.0
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Unless required by applicable law or agreed to in writing, software
							 | 
						||
| 
								 | 
							
								# distributed under the License is distributed on an "AS IS" BASIS,
							 | 
						||
| 
								 | 
							
								# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
							 | 
						||
| 
								 | 
							
								# See the License for the specific language governing permissions and
							 | 
						||
| 
								 | 
							
								# limitations under the License.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								"""utils.py: export utility functions.
							 | 
						||
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from __future__ import print_function
							 | 
						||
| 
								 | 
							
								import logging
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import os.path
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import shutil
							 | 
						||
| 
								 | 
							
								import subprocess
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import time
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_script_dir():
							 | 
						||
| 
								 | 
							
								    return os.path.dirname(os.path.realpath(__file__))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_windows():
							 | 
						||
| 
								 | 
							
								    return sys.platform == 'win32' or sys.platform == 'cygwin'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_darwin():
							 | 
						||
| 
								 | 
							
								    return sys.platform == 'darwin'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_platform():
							 | 
						||
| 
								 | 
							
								    if is_windows():
							 | 
						||
| 
								 | 
							
								        return 'windows'
							 | 
						||
| 
								 | 
							
								    if is_darwin():
							 | 
						||
| 
								 | 
							
								        return 'darwin'
							 | 
						||
| 
								 | 
							
								    return 'linux'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_python3():
							 | 
						||
| 
								 | 
							
								    return sys.version_info >= (3, 0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def log_debug(msg):
							 | 
						||
| 
								 | 
							
								    logging.debug(msg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def log_info(msg):
							 | 
						||
| 
								 | 
							
								    logging.info(msg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def log_warning(msg):
							 | 
						||
| 
								 | 
							
								    logging.warning(msg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def log_fatal(msg):
							 | 
						||
| 
								 | 
							
								    raise Exception(msg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def log_exit(msg):
							 | 
						||
| 
								 | 
							
								    sys.exit(msg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def disable_debug_log():
							 | 
						||
| 
								 | 
							
								    logging.getLogger().setLevel(logging.WARN)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def str_to_bytes(str):
							 | 
						||
| 
								 | 
							
								    if not is_python3():
							 | 
						||
| 
								 | 
							
								        return str
							 | 
						||
| 
								 | 
							
								    # In python 3, str are wide strings whereas the C api expects 8 bit strings,
							 | 
						||
| 
								 | 
							
								    # hence we have to convert. For now using utf-8 as the encoding.
							 | 
						||
| 
								 | 
							
								    return str.encode('utf-8')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def bytes_to_str(bytes):
							 | 
						||
| 
								 | 
							
								    if not is_python3():
							 | 
						||
| 
								 | 
							
								        return bytes
							 | 
						||
| 
								 | 
							
								    return bytes.decode('utf-8')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_target_binary_path(arch, binary_name):
							 | 
						||
| 
								 | 
							
								    if arch == 'aarch64':
							 | 
						||
| 
								 | 
							
								        arch = 'arm64'
							 | 
						||
| 
								 | 
							
								    arch_dir = os.path.join(get_script_dir(), "bin", "android", arch)
							 | 
						||
| 
								 | 
							
								    if not os.path.isdir(arch_dir):
							 | 
						||
| 
								 | 
							
								        log_fatal("can't find arch directory: %s" % arch_dir)
							 | 
						||
| 
								 | 
							
								    binary_path = os.path.join(arch_dir, binary_name)
							 | 
						||
| 
								 | 
							
								    if not os.path.isfile(binary_path):
							 | 
						||
| 
								 | 
							
								        log_fatal("can't find binary: %s" % binary_path)
							 | 
						||
| 
								 | 
							
								    return binary_path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_host_binary_path(binary_name):
							 | 
						||
| 
								 | 
							
								    dir = os.path.join(get_script_dir(), 'bin')
							 | 
						||
| 
								 | 
							
								    if is_windows():
							 | 
						||
| 
								 | 
							
								        if binary_name.endswith('.so'):
							 | 
						||
| 
								 | 
							
								            binary_name = binary_name[0:-3] + '.dll'
							 | 
						||
| 
								 | 
							
								        elif '.' not in binary_name:
							 | 
						||
| 
								 | 
							
								            binary_name += '.exe'
							 | 
						||
| 
								 | 
							
								        dir = os.path.join(dir, 'windows')
							 | 
						||
| 
								 | 
							
								    elif sys.platform == 'darwin': # OSX
							 | 
						||
| 
								 | 
							
								        if binary_name.endswith('.so'):
							 | 
						||
| 
								 | 
							
								            binary_name = binary_name[0:-3] + '.dylib'
							 | 
						||
| 
								 | 
							
								        dir = os.path.join(dir, 'darwin')
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        dir = os.path.join(dir, 'linux')
							 | 
						||
| 
								 | 
							
								    dir = os.path.join(dir, 'x86_64' if sys.maxsize > 2 ** 32 else 'x86')
							 | 
						||
| 
								 | 
							
								    binary_path = os.path.join(dir, binary_name)
							 | 
						||
| 
								 | 
							
								    if not os.path.isfile(binary_path):
							 | 
						||
| 
								 | 
							
								        log_fatal("can't find binary: %s" % binary_path)
							 | 
						||
| 
								 | 
							
								    return binary_path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_executable_available(executable, option='--help'):
							 | 
						||
| 
								 | 
							
								    """ Run an executable to see if it exists. """
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        subproc = subprocess.Popen([executable, option], stdout=subprocess.PIPE,
							 | 
						||
| 
								 | 
							
								                                   stderr=subprocess.PIPE)
							 | 
						||
| 
								 | 
							
								        subproc.communicate()
							 | 
						||
| 
								 | 
							
								        return subproc.returncode == 0
							 | 
						||
| 
								 | 
							
								    except:
							 | 
						||
| 
								 | 
							
								        return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DEFAULT_NDK_PATH = {
							 | 
						||
| 
								 | 
							
								    'darwin': 'Library/Android/sdk/ndk-bundle',
							 | 
						||
| 
								 | 
							
								    'linux': 'Android/Sdk/ndk-bundle',
							 | 
						||
| 
								 | 
							
								    'windows': 'AppData/Local/Android/sdk/ndk-bundle',
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								EXPECTED_TOOLS = {
							 | 
						||
| 
								 | 
							
								    'adb': {
							 | 
						||
| 
								 | 
							
								        'is_binutils': False,
							 | 
						||
| 
								 | 
							
								        'test_option': 'version',
							 | 
						||
| 
								 | 
							
								        'path_in_ndk': '../platform-tools/adb',
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    'readelf': {
							 | 
						||
| 
								 | 
							
								        'is_binutils': True,
							 | 
						||
| 
								 | 
							
								        'accept_tool_without_arch': True,
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    'addr2line': {
							 | 
						||
| 
								 | 
							
								        'is_binutils': True,
							 | 
						||
| 
								 | 
							
								        'accept_tool_without_arch': True
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    'objdump': {
							 | 
						||
| 
								 | 
							
								        'is_binutils': True,
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _get_binutils_path_in_ndk(toolname, arch, platform):
							 | 
						||
| 
								 | 
							
								    if not arch:
							 | 
						||
| 
								 | 
							
								        arch = 'arm64'
							 | 
						||
| 
								 | 
							
								    if arch == 'arm64':
							 | 
						||
| 
								 | 
							
								        name = 'aarch64-linux-android-' + toolname
							 | 
						||
| 
								 | 
							
								        path = 'toolchains/aarch64-linux-android-4.9/prebuilt/%s-x86_64/bin/%s' % (platform, name)
							 | 
						||
| 
								 | 
							
								    elif arch == 'arm':
							 | 
						||
| 
								 | 
							
								        name = 'arm-linux-androideabi-' + toolname
							 | 
						||
| 
								 | 
							
								        path = 'toolchains/arm-linux-androideabi-4.9/prebuilt/%s-x86_64/bin/%s' % (platform, name)
							 | 
						||
| 
								 | 
							
								    elif arch == 'x86_64':
							 | 
						||
| 
								 | 
							
								        name = 'x86_64-linux-android-' + toolname
							 | 
						||
| 
								 | 
							
								        path = 'toolchains/x86_64-4.9/prebuilt/%s-x86_64/bin/%s' % (platform, name)
							 | 
						||
| 
								 | 
							
								    elif arch == 'x86':
							 | 
						||
| 
								 | 
							
								        name = 'i686-linux-android-' + toolname
							 | 
						||
| 
								 | 
							
								        path = 'toolchains/x86-4.9/prebuilt/%s-x86_64/bin/%s' % (platform, name)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        log_fatal('unexpected arch %s' % arch)
							 | 
						||
| 
								 | 
							
								    return (name, path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def find_tool_path(toolname, ndk_path=None, arch=None):
							 | 
						||
| 
								 | 
							
								    if toolname not in EXPECTED_TOOLS:
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								    tool_info = EXPECTED_TOOLS[toolname]
							 | 
						||
| 
								 | 
							
								    is_binutils = tool_info['is_binutils']
							 | 
						||
| 
								 | 
							
								    test_option = tool_info.get('test_option', '--help')
							 | 
						||
| 
								 | 
							
								    platform = get_platform()
							 | 
						||
| 
								 | 
							
								    if is_binutils:
							 | 
						||
| 
								 | 
							
								        toolname_with_arch, path_in_ndk = _get_binutils_path_in_ndk(toolname, arch, platform)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        toolname_with_arch = toolname
							 | 
						||
| 
								 | 
							
								        path_in_ndk = tool_info['path_in_ndk']
							 | 
						||
| 
								 | 
							
								    path_in_ndk = path_in_ndk.replace('/', os.sep)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # 1. Find tool in the given ndk path.
							 | 
						||
| 
								 | 
							
								    if ndk_path:
							 | 
						||
| 
								 | 
							
								        path = os.path.join(ndk_path, path_in_ndk)
							 | 
						||
| 
								 | 
							
								        if is_executable_available(path, test_option):
							 | 
						||
| 
								 | 
							
								            return path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # 2. Find tool in the ndk directory containing simpleperf scripts.
							 | 
						||
| 
								 | 
							
								    path = os.path.join('..', path_in_ndk)
							 | 
						||
| 
								 | 
							
								    if is_executable_available(path, test_option):
							 | 
						||
| 
								 | 
							
								        return path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # 3. Find tool in the default ndk installation path.
							 | 
						||
| 
								 | 
							
								    home = os.environ.get('HOMEPATH') if is_windows() else os.environ.get('HOME')
							 | 
						||
| 
								 | 
							
								    if home:
							 | 
						||
| 
								 | 
							
								        default_ndk_path = os.path.join(home, DEFAULT_NDK_PATH[platform].replace('/', os.sep))
							 | 
						||
| 
								 | 
							
								        path = os.path.join(default_ndk_path, path_in_ndk)
							 | 
						||
| 
								 | 
							
								        if is_executable_available(path, test_option):
							 | 
						||
| 
								 | 
							
								            return path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # 4. Find tool in $PATH.
							 | 
						||
| 
								 | 
							
								    if is_executable_available(toolname_with_arch, test_option):
							 | 
						||
| 
								 | 
							
								        return toolname_with_arch
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # 5. Find tool without arch in $PATH.
							 | 
						||
| 
								 | 
							
								    if is_binutils and tool_info.get('accept_tool_without_arch'):
							 | 
						||
| 
								 | 
							
								        if is_executable_available(toolname, test_option):
							 | 
						||
| 
								 | 
							
								            return toolname
							 | 
						||
| 
								 | 
							
								    return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class AdbHelper(object):
							 | 
						||
| 
								 | 
							
								    def __init__(self, enable_switch_to_root=True):
							 | 
						||
| 
								 | 
							
								        adb_path = find_tool_path('adb')
							 | 
						||
| 
								 | 
							
								        if not adb_path:
							 | 
						||
| 
								 | 
							
								            log_exit("Can't find adb in PATH environment.")
							 | 
						||
| 
								 | 
							
								        self.adb_path = adb_path
							 | 
						||
| 
								 | 
							
								        self.enable_switch_to_root = enable_switch_to_root
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def run(self, adb_args):
							 | 
						||
| 
								 | 
							
								        return self.run_and_return_output(adb_args)[0]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def run_and_return_output(self, adb_args, stdout_file=None, log_output=True):
							 | 
						||
| 
								 | 
							
								        adb_args = [self.adb_path] + adb_args
							 | 
						||
| 
								 | 
							
								        log_debug('run adb cmd: %s' % adb_args)
							 | 
						||
| 
								 | 
							
								        if stdout_file:
							 | 
						||
| 
								 | 
							
								            with open(stdout_file, 'wb') as stdout_fh:
							 | 
						||
| 
								 | 
							
								                returncode = subprocess.call(adb_args, stdout=stdout_fh)
							 | 
						||
| 
								 | 
							
								            stdoutdata = ''
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            subproc = subprocess.Popen(adb_args, stdout=subprocess.PIPE)
							 | 
						||
| 
								 | 
							
								            (stdoutdata, _) = subproc.communicate()
							 | 
						||
| 
								 | 
							
								            returncode = subproc.returncode
							 | 
						||
| 
								 | 
							
								        result = (returncode == 0)
							 | 
						||
| 
								 | 
							
								        if stdoutdata and adb_args[1] != 'push' and adb_args[1] != 'pull':
							 | 
						||
| 
								 | 
							
								            stdoutdata = bytes_to_str(stdoutdata)
							 | 
						||
| 
								 | 
							
								            if log_output:
							 | 
						||
| 
								 | 
							
								                log_debug(stdoutdata)
							 | 
						||
| 
								 | 
							
								        log_debug('run adb cmd: %s  [result %s]' % (adb_args, result))
							 | 
						||
| 
								 | 
							
								        return (result, stdoutdata)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def check_run(self, adb_args):
							 | 
						||
| 
								 | 
							
								        self.check_run_and_return_output(adb_args)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def check_run_and_return_output(self, adb_args, stdout_file=None, log_output=True):
							 | 
						||
| 
								 | 
							
								        result, stdoutdata = self.run_and_return_output(adb_args, stdout_file, log_output)
							 | 
						||
| 
								 | 
							
								        if not result:
							 | 
						||
| 
								 | 
							
								            log_exit('run "adb %s" failed' % adb_args)
							 | 
						||
| 
								 | 
							
								        return stdoutdata
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _unroot(self):
							 | 
						||
| 
								 | 
							
								        result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
							 | 
						||
| 
								 | 
							
								        if not result:
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        if 'root' not in stdoutdata:
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        log_info('unroot adb')
							 | 
						||
| 
								 | 
							
								        self.run(['unroot'])
							 | 
						||
| 
								 | 
							
								        self.run(['wait-for-device'])
							 | 
						||
| 
								 | 
							
								        time.sleep(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def switch_to_root(self):
							 | 
						||
| 
								 | 
							
								        if not self.enable_switch_to_root:
							 | 
						||
| 
								 | 
							
								            self._unroot()
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
							 | 
						||
| 
								 | 
							
								        if not result:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        if 'root' in stdoutdata:
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								        build_type = self.get_property('ro.build.type')
							 | 
						||
| 
								 | 
							
								        if build_type == 'user':
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        self.run(['root'])
							 | 
						||
| 
								 | 
							
								        time.sleep(1)
							 | 
						||
| 
								 | 
							
								        self.run(['wait-for-device'])
							 | 
						||
| 
								 | 
							
								        result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
							 | 
						||
| 
								 | 
							
								        return result and 'root' in stdoutdata
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_property(self, name):
							 | 
						||
| 
								 | 
							
								        result, stdoutdata = self.run_and_return_output(['shell', 'getprop', name])
							 | 
						||
| 
								 | 
							
								        return stdoutdata if result else None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def set_property(self, name, value):
							 | 
						||
| 
								 | 
							
								        return self.run(['shell', 'setprop', name, value])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_device_arch(self):
							 | 
						||
| 
								 | 
							
								        output = self.check_run_and_return_output(['shell', 'uname', '-m'])
							 | 
						||
| 
								 | 
							
								        if 'aarch64' in output:
							 | 
						||
| 
								 | 
							
								            return 'arm64'
							 | 
						||
| 
								 | 
							
								        if 'arm' in output:
							 | 
						||
| 
								 | 
							
								            return 'arm'
							 | 
						||
| 
								 | 
							
								        if 'x86_64' in output:
							 | 
						||
| 
								 | 
							
								            return 'x86_64'
							 | 
						||
| 
								 | 
							
								        if '86' in output:
							 | 
						||
| 
								 | 
							
								            return 'x86'
							 | 
						||
| 
								 | 
							
								        log_fatal('unsupported architecture: %s' % output.strip())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_android_version(self):
							 | 
						||
| 
								 | 
							
								        build_version = self.get_property('ro.build.version.release')
							 | 
						||
| 
								 | 
							
								        android_version = 0
							 | 
						||
| 
								 | 
							
								        if build_version:
							 | 
						||
| 
								 | 
							
								            if not build_version[0].isdigit():
							 | 
						||
| 
								 | 
							
								                c = build_version[0].upper()
							 | 
						||
| 
								 | 
							
								                if c.isupper() and c >= 'L':
							 | 
						||
| 
								 | 
							
								                    android_version = ord(c) - ord('L') + 5
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                strs = build_version.split('.')
							 | 
						||
| 
								 | 
							
								                if strs:
							 | 
						||
| 
								 | 
							
								                    android_version = int(strs[0])
							 | 
						||
| 
								 | 
							
								        return android_version
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def flatten_arg_list(arg_list):
							 | 
						||
| 
								 | 
							
								    res = []
							 | 
						||
| 
								 | 
							
								    if arg_list:
							 | 
						||
| 
								 | 
							
								        for items in arg_list:
							 | 
						||
| 
								 | 
							
								            res += items
							 | 
						||
| 
								 | 
							
								    return res
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def remove(dir_or_file):
							 | 
						||
| 
								 | 
							
								    if os.path.isfile(dir_or_file):
							 | 
						||
| 
								 | 
							
								        os.remove(dir_or_file)
							 | 
						||
| 
								 | 
							
								    elif os.path.isdir(dir_or_file):
							 | 
						||
| 
								 | 
							
								        shutil.rmtree(dir_or_file, ignore_errors=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def open_report_in_browser(report_path):
							 | 
						||
| 
								 | 
							
								    if is_darwin():
							 | 
						||
| 
								 | 
							
								        # On darwin 10.12.6, webbrowser can't open browser, so try `open` cmd first.
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            subprocess.check_call(['open', report_path])
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        except:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								    import webbrowser
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        # Try to open the report with Chrome
							 | 
						||
| 
								 | 
							
								        browser_key = ''
							 | 
						||
| 
								 | 
							
								        for key, _ in webbrowser._browsers.items():
							 | 
						||
| 
								 | 
							
								            if 'chrome' in key:
							 | 
						||
| 
								 | 
							
								                browser_key = key
							 | 
						||
| 
								 | 
							
								        browser = webbrowser.get(browser_key)
							 | 
						||
| 
								 | 
							
								        browser.open(report_path, new=0, autoraise=True)
							 | 
						||
| 
								 | 
							
								    except:
							 | 
						||
| 
								 | 
							
								        # webbrowser.get() doesn't work well on darwin/windows.
							 | 
						||
| 
								 | 
							
								        webbrowser.open_new_tab(report_path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def find_real_dso_path(dso_path_in_record_file, binary_cache_path):
							 | 
						||
| 
								 | 
							
								    """ Given the path of a shared library in perf.data, find its real path in the file system. """
							 | 
						||
| 
								 | 
							
								    if dso_path_in_record_file[0] != '/' or dso_path_in_record_file == '//anon':
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								    if binary_cache_path:
							 | 
						||
| 
								 | 
							
								        tmp_path = os.path.join(binary_cache_path, dso_path_in_record_file[1:])
							 | 
						||
| 
								 | 
							
								        if os.path.isfile(tmp_path):
							 | 
						||
| 
								 | 
							
								            return tmp_path
							 | 
						||
| 
								 | 
							
								    if os.path.isfile(dso_path_in_record_file):
							 | 
						||
| 
								 | 
							
								        return dso_path_in_record_file
							 | 
						||
| 
								 | 
							
								    return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Addr2Nearestline(object):
							 | 
						||
| 
								 | 
							
								    """ Use addr2line to convert (dso_path, func_addr, addr) to (source_file, line) pairs.
							 | 
						||
| 
								 | 
							
								        For instructions generated by C++ compilers without a matching statement in source code
							 | 
						||
| 
								 | 
							
								        (like stack corruption check, switch optimization, etc.), addr2line can't generate
							 | 
						||
| 
								 | 
							
								        line information. However, we want to assign the instruction to the nearest line before
							 | 
						||
| 
								 | 
							
								        the instruction (just like objdump -dl). So we use below strategy:
							 | 
						||
| 
								 | 
							
								        Instead of finding the exact line of the instruction in an address, we find the nearest
							 | 
						||
| 
								 | 
							
								        line to the instruction in an address. If an address doesn't have a line info, we find
							 | 
						||
| 
								 | 
							
								        the line info of address - 1. If still no line info, then use address - 2, address - 3,
							 | 
						||
| 
								 | 
							
								        etc.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        The implementation steps are as below:
							 | 
						||
| 
								 | 
							
								        1. Collect all (dso_path, func_addr, addr) requests before converting. This saves the
							 | 
						||
| 
								 | 
							
								        times to call addr2line.
							 | 
						||
| 
								 | 
							
								        2. Convert addrs to (source_file, line) pairs for each dso_path as below:
							 | 
						||
| 
								 | 
							
								          2.1 Check if the dso_path has .debug_line. If not, omit its conversion.
							 | 
						||
| 
								 | 
							
								          2.2 Get arch of the dso_path, and decide the addr_step for it. addr_step is the step we
							 | 
						||
| 
								 | 
							
								          change addr each time. For example, since instructions of arm64 are all 4 bytes long,
							 | 
						||
| 
								 | 
							
								          addr_step for arm64 can be 4.
							 | 
						||
| 
								 | 
							
								          2.3 Use addr2line to find line info for each addr in the dso_path.
							 | 
						||
| 
								 | 
							
								          2.4 For each addr without line info, use addr2line to find line info for
							 | 
						||
| 
								 | 
							
								              range(addr - addr_step, addr - addr_step * 4 - 1, -addr_step).
							 | 
						||
| 
								 | 
							
								          2.5 For each addr without line info, use addr2line to find line info for
							 | 
						||
| 
								 | 
							
								              range(addr - addr_step * 5, addr - addr_step * 128 - 1, -addr_step).
							 | 
						||
| 
								 | 
							
								              (128 is a guess number. A nested switch statement in
							 | 
						||
| 
								 | 
							
								               system/core/demangle/Demangler.cpp has >300 bytes without line info in arm64.)
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    class Dso(object):
							 | 
						||
| 
								 | 
							
								        """ Info of a dynamic shared library.
							 | 
						||
| 
								 | 
							
								            addrs: a map from address to Addr object in this dso.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        def __init__(self):
							 | 
						||
| 
								 | 
							
								            self.addrs = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    class Addr(object):
							 | 
						||
| 
								 | 
							
								        """ Info of an addr request.
							 | 
						||
| 
								 | 
							
								            func_addr: start_addr of the function containing addr.
							 | 
						||
| 
								 | 
							
								            source_lines: a list of [file_id, line_number] for addr.
							 | 
						||
| 
								 | 
							
								                          source_lines[:-1] are all for inlined functions.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        def __init__(self, func_addr):
							 | 
						||
| 
								 | 
							
								            self.func_addr = func_addr
							 | 
						||
| 
								 | 
							
								            self.source_lines = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, ndk_path, binary_cache_path):
							 | 
						||
| 
								 | 
							
								        self.addr2line_path = find_tool_path('addr2line', ndk_path)
							 | 
						||
| 
								 | 
							
								        if not self.addr2line_path:
							 | 
						||
| 
								 | 
							
								            log_exit("Can't find addr2line. Please set ndk path with --ndk-path option.")
							 | 
						||
| 
								 | 
							
								        self.readelf = ReadElf(ndk_path)
							 | 
						||
| 
								 | 
							
								        self.dso_map = {}  # map from dso_path to Dso.
							 | 
						||
| 
								 | 
							
								        self.binary_cache_path = binary_cache_path
							 | 
						||
| 
								 | 
							
								        # Saving file names for each addr takes a lot of memory. So we store file ids in Addr,
							 | 
						||
| 
								 | 
							
								        # and provide data structures connecting file id and file name here.
							 | 
						||
| 
								 | 
							
								        self.file_name_to_id = {}
							 | 
						||
| 
								 | 
							
								        self.file_id_to_name = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def add_addr(self, dso_path, func_addr, addr):
							 | 
						||
| 
								 | 
							
								        dso = self.dso_map.get(dso_path)
							 | 
						||
| 
								 | 
							
								        if dso is None:
							 | 
						||
| 
								 | 
							
								            dso = self.dso_map[dso_path] = self.Dso()
							 | 
						||
| 
								 | 
							
								        if addr not in dso.addrs:
							 | 
						||
| 
								 | 
							
								            dso.addrs[addr] = self.Addr(func_addr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def convert_addrs_to_lines(self):
							 | 
						||
| 
								 | 
							
								        for dso_path in self.dso_map:
							 | 
						||
| 
								 | 
							
								            self._convert_addrs_in_one_dso(dso_path, self.dso_map[dso_path])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _convert_addrs_in_one_dso(self, dso_path, dso):
							 | 
						||
| 
								 | 
							
								        real_path = find_real_dso_path(dso_path, self.binary_cache_path)
							 | 
						||
| 
								 | 
							
								        if not real_path:
							 | 
						||
| 
								 | 
							
								            if dso_path not in ['//anon', 'unknown', '[kernel.kallsyms]']:
							 | 
						||
| 
								 | 
							
								                log_debug("Can't find dso %s" % dso_path)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not self._check_debug_line_section(real_path):
							 | 
						||
| 
								 | 
							
								            log_debug("file %s doesn't contain .debug_line section." % real_path)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        addr_step = self._get_addr_step(real_path)
							 | 
						||
| 
								 | 
							
								        self._collect_line_info(dso, real_path, [0])
							 | 
						||
| 
								 | 
							
								        self._collect_line_info(dso, real_path, range(-addr_step, -addr_step * 4 - 1, -addr_step))
							 | 
						||
| 
								 | 
							
								        self._collect_line_info(dso, real_path,
							 | 
						||
| 
								 | 
							
								                                range(-addr_step * 5, -addr_step * 128 - 1, -addr_step))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _check_debug_line_section(self, real_path):
							 | 
						||
| 
								 | 
							
								        return '.debug_line' in self.readelf.get_sections(real_path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _get_addr_step(self, real_path):
							 | 
						||
| 
								 | 
							
								        arch = self.readelf.get_arch(real_path)
							 | 
						||
| 
								 | 
							
								        if arch == 'arm64':
							 | 
						||
| 
								 | 
							
								            return 4
							 | 
						||
| 
								 | 
							
								        if arch == 'arm':
							 | 
						||
| 
								 | 
							
								            return 2
							 | 
						||
| 
								 | 
							
								        return 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _collect_line_info(self, dso, real_path, addr_shifts):
							 | 
						||
| 
								 | 
							
								        """ Use addr2line to get line info in a dso, with given addr shifts. """
							 | 
						||
| 
								 | 
							
								        # 1. Collect addrs to send to addr2line.
							 | 
						||
| 
								 | 
							
								        addr_set = set()
							 | 
						||
| 
								 | 
							
								        for addr in dso.addrs:
							 | 
						||
| 
								 | 
							
								            addr_obj = dso.addrs[addr]
							 | 
						||
| 
								 | 
							
								            if addr_obj.source_lines:  # already has source line, no need to search.
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            for shift in addr_shifts:
							 | 
						||
| 
								 | 
							
								                # The addr after shift shouldn't change to another function.
							 | 
						||
| 
								 | 
							
								                shifted_addr = max(addr + shift, addr_obj.func_addr)
							 | 
						||
| 
								 | 
							
								                addr_set.add(shifted_addr)
							 | 
						||
| 
								 | 
							
								                if shifted_addr == addr_obj.func_addr:
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								        if not addr_set:
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        addr_request = '\n'.join(['%x' % addr for addr in sorted(addr_set)])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # 2. Use addr2line to collect line info.
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            subproc = subprocess.Popen([self.addr2line_path, '-ai', '-e', real_path],
							 | 
						||
| 
								 | 
							
								                                       stdin=subprocess.PIPE, stdout=subprocess.PIPE)
							 | 
						||
| 
								 | 
							
								            (stdoutdata, _) = subproc.communicate(str_to_bytes(addr_request))
							 | 
						||
| 
								 | 
							
								            stdoutdata = bytes_to_str(stdoutdata)
							 | 
						||
| 
								 | 
							
								        except:
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								        addr_map = {}
							 | 
						||
| 
								 | 
							
								        cur_line_list = None
							 | 
						||
| 
								 | 
							
								        for line in stdoutdata.strip().split('\n'):
							 | 
						||
| 
								 | 
							
								            if line[:2] == '0x':
							 | 
						||
| 
								 | 
							
								                # a new address
							 | 
						||
| 
								 | 
							
								                cur_line_list = addr_map[int(line, 16)] = []
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # a file:line.
							 | 
						||
| 
								 | 
							
								                if cur_line_list is None:
							 | 
						||
| 
								 | 
							
								                    continue
							 | 
						||
| 
								 | 
							
								                # Handle lines like "C:\Users\...\file:32".
							 | 
						||
| 
								 | 
							
								                items = line.rsplit(':', 1)
							 | 
						||
| 
								 | 
							
								                if len(items) != 2:
							 | 
						||
| 
								 | 
							
								                    continue
							 | 
						||
| 
								 | 
							
								                if '?' in line:
							 | 
						||
| 
								 | 
							
								                    # if ? in line, it doesn't have a valid line info.
							 | 
						||
| 
								 | 
							
								                    # An addr can have a list of (file, line), when the addr belongs to an inlined
							 | 
						||
| 
								 | 
							
								                    # function. Sometimes only part of the list has ? mark. In this case, we think
							 | 
						||
| 
								 | 
							
								                    # the line info is valid if the first line doesn't have ? mark.
							 | 
						||
| 
								 | 
							
								                    if not cur_line_list:
							 | 
						||
| 
								 | 
							
								                        cur_line_list = None
							 | 
						||
| 
								 | 
							
								                    continue
							 | 
						||
| 
								 | 
							
								                (file_path, line_number) = items
							 | 
						||
| 
								 | 
							
								                line_number = line_number.split()[0]  # Remove comments after line number
							 | 
						||
| 
								 | 
							
								                try:
							 | 
						||
| 
								 | 
							
								                    line_number = int(line_number)
							 | 
						||
| 
								 | 
							
								                except ValueError:
							 | 
						||
| 
								 | 
							
								                    continue
							 | 
						||
| 
								 | 
							
								                file_id = self._get_file_id(file_path)
							 | 
						||
| 
								 | 
							
								                cur_line_list.append((file_id, line_number))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # 3. Fill line info in dso.addrs.
							 | 
						||
| 
								 | 
							
								        for addr in dso.addrs:
							 | 
						||
| 
								 | 
							
								            addr_obj = dso.addrs[addr]
							 | 
						||
| 
								 | 
							
								            if addr_obj.source_lines:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            for shift in addr_shifts:
							 | 
						||
| 
								 | 
							
								                shifted_addr = max(addr + shift, addr_obj.func_addr)
							 | 
						||
| 
								 | 
							
								                lines = addr_map.get(shifted_addr)
							 | 
						||
| 
								 | 
							
								                if lines:
							 | 
						||
| 
								 | 
							
								                    addr_obj.source_lines = lines
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								                if shifted_addr == addr_obj.func_addr:
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _get_file_id(self, file_path):
							 | 
						||
| 
								 | 
							
								        file_id = self.file_name_to_id.get(file_path)
							 | 
						||
| 
								 | 
							
								        if file_id is None:
							 | 
						||
| 
								 | 
							
								            file_id = self.file_name_to_id[file_path] = len(self.file_id_to_name)
							 | 
						||
| 
								 | 
							
								            self.file_id_to_name.append(file_path)
							 | 
						||
| 
								 | 
							
								        return file_id
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_dso(self, dso_path):
							 | 
						||
| 
								 | 
							
								        return self.dso_map.get(dso_path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_addr_source(self, dso, addr):
							 | 
						||
| 
								 | 
							
								        source = dso.addrs[addr].source_lines
							 | 
						||
| 
								 | 
							
								        if source is None:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        return [(self.file_id_to_name[file_id], line) for (file_id, line) in source]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Objdump(object):
							 | 
						||
| 
								 | 
							
								    """ A wrapper of objdump to disassemble code. """
							 | 
						||
| 
								 | 
							
								    def __init__(self, ndk_path, binary_cache_path):
							 | 
						||
| 
								 | 
							
								        self.ndk_path = ndk_path
							 | 
						||
| 
								 | 
							
								        self.binary_cache_path = binary_cache_path
							 | 
						||
| 
								 | 
							
								        self.readelf = ReadElf(ndk_path)
							 | 
						||
| 
								 | 
							
								        self.objdump_paths = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def disassemble_code(self, dso_path, start_addr, addr_len):
							 | 
						||
| 
								 | 
							
								        """ Disassemble [start_addr, start_addr + addr_len] of dso_path.
							 | 
						||
| 
								 | 
							
								            Return a list of pair (disassemble_code_line, addr).
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # 1. Find real path.
							 | 
						||
| 
								 | 
							
								        real_path = find_real_dso_path(dso_path, self.binary_cache_path)
							 | 
						||
| 
								 | 
							
								        if real_path is None:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # 2. Get path of objdump.
							 | 
						||
| 
								 | 
							
								        arch = self.readelf.get_arch(real_path)
							 | 
						||
| 
								 | 
							
								        if arch == 'unknown':
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        objdump_path = self.objdump_paths.get(arch)
							 | 
						||
| 
								 | 
							
								        if not objdump_path:
							 | 
						||
| 
								 | 
							
								            objdump_path = find_tool_path('objdump', self.ndk_path, arch)
							 | 
						||
| 
								 | 
							
								            if not objdump_path:
							 | 
						||
| 
								 | 
							
								                log_exit("Can't find objdump. Please set ndk path with --ndk_path option.")
							 | 
						||
| 
								 | 
							
								            self.objdump_paths[arch] = objdump_path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # 3. Run objdump.
							 | 
						||
| 
								 | 
							
								        args = [objdump_path, '-dlC', '--no-show-raw-insn',
							 | 
						||
| 
								 | 
							
								                '--start-address=0x%x' % start_addr,
							 | 
						||
| 
								 | 
							
								                '--stop-address=0x%x' % (start_addr + addr_len),
							 | 
						||
| 
								 | 
							
								                real_path]
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            subproc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
							 | 
						||
| 
								 | 
							
								            (stdoutdata, _) = subproc.communicate()
							 | 
						||
| 
								 | 
							
								            stdoutdata = bytes_to_str(stdoutdata)
							 | 
						||
| 
								 | 
							
								        except:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not stdoutdata:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        result = []
							 | 
						||
| 
								 | 
							
								        for line in stdoutdata.split('\n'):
							 | 
						||
| 
								 | 
							
								            line = line.rstrip()  # Remove '\r' on Windows.
							 | 
						||
| 
								 | 
							
								            items = line.split(':', 1)
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                addr = int(items[0], 16)
							 | 
						||
| 
								 | 
							
								            except ValueError:
							 | 
						||
| 
								 | 
							
								                addr = 0
							 | 
						||
| 
								 | 
							
								            result.append((line, addr))
							 | 
						||
| 
								 | 
							
								        return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class ReadElf(object):
							 | 
						||
| 
								 | 
							
								    """ A wrapper of readelf. """
							 | 
						||
| 
								 | 
							
								    def __init__(self, ndk_path):
							 | 
						||
| 
								 | 
							
								        self.readelf_path = find_tool_path('readelf', ndk_path)
							 | 
						||
| 
								 | 
							
								        if not self.readelf_path:
							 | 
						||
| 
								 | 
							
								            log_exit("Can't find readelf. Please set ndk path with --ndk_path option.")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_arch(self, elf_file_path):
							 | 
						||
| 
								 | 
							
								        """ Get arch of an elf file. """
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            output = subprocess.check_output([self.readelf_path, '-h', elf_file_path])
							 | 
						||
| 
								 | 
							
								            if output.find('AArch64') != -1:
							 | 
						||
| 
								 | 
							
								                return 'arm64'
							 | 
						||
| 
								 | 
							
								            if output.find('ARM') != -1:
							 | 
						||
| 
								 | 
							
								                return 'arm'
							 | 
						||
| 
								 | 
							
								            if output.find('X86-64') != -1:
							 | 
						||
| 
								 | 
							
								                return 'x86_64'
							 | 
						||
| 
								 | 
							
								            if output.find('80386') != -1:
							 | 
						||
| 
								 | 
							
								                return 'x86'
							 | 
						||
| 
								 | 
							
								        except subprocess.CalledProcessError:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								        return 'unknown'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_build_id(self, elf_file_path):
							 | 
						||
| 
								 | 
							
								        """ Get build id of an elf file. """
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            output = subprocess.check_output([self.readelf_path, '-n', elf_file_path])
							 | 
						||
| 
								 | 
							
								            output = bytes_to_str(output)
							 | 
						||
| 
								 | 
							
								            result = re.search(r'Build ID:\s*(\S+)', output)
							 | 
						||
| 
								 | 
							
								            if result:
							 | 
						||
| 
								 | 
							
								                build_id = result.group(1)
							 | 
						||
| 
								 | 
							
								                if len(build_id) < 40:
							 | 
						||
| 
								 | 
							
								                    build_id += '0' * (40 - len(build_id))
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    build_id = build_id[:40]
							 | 
						||
| 
								 | 
							
								                build_id = '0x' + build_id
							 | 
						||
| 
								 | 
							
								                return build_id
							 | 
						||
| 
								 | 
							
								        except subprocess.CalledProcessError:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								        return ""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_sections(self, elf_file_path):
							 | 
						||
| 
								 | 
							
								        """ Get sections of an elf file. """
							 | 
						||
| 
								 | 
							
								        section_names = []
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            output = subprocess.check_output([self.readelf_path, '-SW', elf_file_path])
							 | 
						||
| 
								 | 
							
								            output = bytes_to_str(output)
							 | 
						||
| 
								 | 
							
								            for line in output.split('\n'):
							 | 
						||
| 
								 | 
							
								                # Parse line like:" [ 1] .note.android.ident NOTE  0000000000400190 ...".
							 | 
						||
| 
								 | 
							
								                result = re.search(r'^\s+\[\s*\d+\]\s(.+?)\s', line)
							 | 
						||
| 
								 | 
							
								                if result:
							 | 
						||
| 
								 | 
							
								                    section_name = result.group(1).strip()
							 | 
						||
| 
								 | 
							
								                    if section_name:
							 | 
						||
| 
								 | 
							
								                        section_names.append(section_name)
							 | 
						||
| 
								 | 
							
								        except subprocess.CalledProcessError:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								        return section_names
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def extant_dir(arg):
							 | 
						||
| 
								 | 
							
								    """ArgumentParser type that only accepts extant directories.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Args:
							 | 
						||
| 
								 | 
							
								        arg: The string argument given on the command line.
							 | 
						||
| 
								 | 
							
								    Returns: The argument as a realpath.
							 | 
						||
| 
								 | 
							
								    Raises:
							 | 
						||
| 
								 | 
							
								        argparse.ArgumentTypeError: The given path isn't a directory.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    path = os.path.realpath(arg)
							 | 
						||
| 
								 | 
							
								    if not os.path.isdir(path):
							 | 
						||
| 
								 | 
							
								        import argparse
							 | 
						||
| 
								 | 
							
								        raise argparse.ArgumentTypeError('{} is not a directory.'.format(path))
							 | 
						||
| 
								 | 
							
								    return path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								logging.getLogger().setLevel(logging.DEBUG)
							 |