1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright (c) 2021 Huawei Device Co., Ltd. 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16import argparse 17import os 18import shutil 19 20from ctypes import * 21from hiperf_utils import HdcInterface, get_build_id 22from hiperf_utils import dir_check 23from hiperf_utils import file_check 24from hiperf_utils import get_lib 25from hiperf_utils import get_arg_list 26 27ABS_PATH = os.path.split(os.path.realpath(__file__))[0] 28 29 30class GetLibFiles(object): 31 """Collect all binaries needed by perf.data in binary_cache.""" 32 33 def __init__(self): 34 self.local_cache_dir = os.path.join(ABS_PATH, 'binary_cache') 35 if not os.path.isdir(self.local_cache_dir): 36 os.makedirs(self.local_cache_dir) 37 self.binary_map = {} 38 39 def recv_binary_cache(self, perf_data, lib_dirs): 40 self.get_used_binaries(perf_data) 41 self.copy_binaries_from_lib_dirs(lib_dirs) 42 self.hdc = HdcInterface() 43 self.recv_binaries_from_device() 44 self.recv_kernel_symbols() 45 46 def get_used_binaries(self, perf_data): 47 """read perf.data, get all used binaries and their build id 48 (if available).""" 49 # A dict mapping from binary name to build_id 50 lib_hiperf_report = get_lib() 51 lib_hiperf_report.ReportGetSymbolFiles.restype = c_char_p 52 lib_hiperf_report.ReportGetSymbolFiles.argtypes = [c_char_p] 53 ret = lib_hiperf_report.ReportGetSymbolFiles(perf_data.encode()) 54 55 dso_build_id = str(ret, encoding="utf-8") 56 dso_list = dso_build_id.split('],[') 57 dso_list[0] = dso_list[0][1:] 58 dso_list[-1] = dso_list[-1][0:-1] 59 dso_dict = {} 60 for i in dso_list: 61 group = i.split(',') 62 if len(group) == 2: 63 dso_dict[group[0]] = group[1] 64 else: 65 dso_dict[group[0]] = None 66 self.binary_map = dso_dict 67 68 # open source 69 def copy_binaries_from_lib_dirs(self, lib_dirs): 70 """collect all files in lib_dirs.""" 71 if not lib_dirs: 72 return 73 74 # key is binary name , value is path list 75 file_dict = {} 76 self.get_local_bin_map(file_dict) 77 78 for lib_dir in lib_dirs: 79 for root, _, files in os.walk(lib_dir): 80 self.confirm_copy_file(root, files, file_dict) 81 82 def get_local_bin_map(self, file_dict): 83 for bin_file in self.binary_map: 84 # if path is /system/lib/libhi_irq.so , get libhi_irq.so 85 filename = bin_file[bin_file.rfind('/') + 1:] 86 if file_dict.get(filename) is None: 87 file_dict[filename] = [bin_file] 88 else: 89 file_dict[filename].append(bin_file) 90 91 def confirm_copy_file(self, root, files, file_dict): 92 for filename in files: 93 paths = file_dict.get(filename) 94 if not paths: 95 continue 96 97 build_id = get_build_id(os.path.join(root, filename)) 98 if not build_id: 99 continue 100 101 for bin_file in paths: 102 req_build_id = self.binary_map.get(bin_file) 103 if req_build_id == build_id: 104 self.copy_to_binary_cache( 105 os.path.join(root, filename), bin_file) 106 break 107 108 def copy_to_binary_cache(self, from_path, target_file): 109 if target_file[0] == '/': 110 target_file = target_file[1:] 111 112 target_file = target_file.replace('/', os.sep) 113 target_file = os.path.join(self.local_cache_dir, target_file) 114 target_dir = os.path.dirname(target_file) 115 116 if not os.path.isdir(target_dir): 117 os.makedirs(target_dir) 118 print('copy to binary_cache: %s to %s' % (from_path, target_file)) 119 shutil.copy(from_path, target_file) 120 121 def recv_binaries_from_device(self): 122 """pull binaries needed in perf.data to binary_cache.""" 123 for binary in self.binary_map: 124 # [kernel.kallsyms] or something can't find binary. 125 if not binary.startswith('/') or \ 126 binary.startswith("/dev/" or binary == "//anon"): 127 continue 128 # fit all platform 129 binary_cache_file = binary[1:].replace('/', os.sep) 130 local_cache_file = os.path.join(self.local_cache_dir, 131 binary_cache_file) 132 self.check_and_recv_binary(binary, local_cache_file) 133 134 def check_and_recv_binary(self, binary, local_cache_file): 135 """If the binary_cache_file exists and has the expected_build_id, there 136 is no need to pull the binary from device. Otherwise, pull it. 137 """ 138 req_build_id = self.binary_map[binary] 139 need_pull = True 140 # compare with build id adjust is match file 141 if os.path.isfile(local_cache_file): 142 need_pull = False 143 build_id = get_build_id(local_cache_file) 144 if req_build_id != build_id: 145 print('local file build id is %s is not request build %s' 146 % (build_id,req_build_id)) 147 need_pull = True 148 149 if need_pull: 150 target_dir = os.path.dirname(local_cache_file) 151 if not os.path.isdir(target_dir): 152 os.makedirs(target_dir) 153 if os.path.isfile(local_cache_file): 154 os.remove(local_cache_file) 155 self.pull_file_from_device(binary, local_cache_file) 156 print('recv file to binary_cache: %s to %s' % (binary, 157 local_cache_file)) 158 self.confirm_del_file(binary,local_cache_file) 159 else: 160 print('not need recv, use host file in binary_cache: %s' % 161 local_cache_file) 162 163 def pull_file_from_device(self, device_path, host_path): 164 if self.hdc.run_hdc_cmd(['file recv', device_path, host_path]): 165 return True 166 # In non-root device, we can't pull /data/app/XXX/base.odex directly. 167 # Instead, we can first copy the file to /data/local/tmp, then pull it. 168 filename = device_path[device_path.rfind('/') + 1:] 169 if (self.hdc.run_hdc_cmd(['shell', 'cp', device_path, 170 '/data/local/tmp']) and 171 self.hdc.run_hdc_cmd(['file recv', 172 os.path.join('/data/local/tmp/', filename), 173 host_path])): 174 self.hdc.run_hdc_cmd(['shell', 'rm', 175 os.path.join('/data/local/tmp/', filename)]) 176 return True 177 print('failed to pull %s from device' % device_path) 178 return False 179 180 def confirm_del_file(self, device_path,host_path): 181 build_id = get_build_id(os.path.join(host_path)) 182 if not build_id: 183 return 184 req_build_id = self.binary_map.get(device_path) 185 if not req_build_id == build_id: 186 print('recv file %s build id %s not match request build id %s,' 187 ' delete it' % (host_path,build_id,req_build_id)) 188 os.remove(host_path) 189 190 191 def recv_kernel_symbols(self): 192 file_path = os.path.join(self.local_cache_dir, 'kallsyms') 193 if os.path.isfile(file_path): 194 os.remove(file_path) 195 if self.hdc.switch_root(): 196 old_kptr_restrict = os.popen('hdc shell cat /proc/sys/kernel/kptr_restrict').read() 197 self.hdc.run_hdc_cmd(['shell', 198 '"echo 0 >/proc/sys/kernel/kptr_restrict"']) 199 self.hdc.run_hdc_cmd(['file recv', '/proc/kallsyms', file_path]) 200 self.hdc.run_hdc_cmd(['shell', 201 '"echo ' + old_kptr_restrict[0] + ' >/proc/sys/kernel/kptr_restrict"']) 202 203 204def main(): 205 parser = argparse.ArgumentParser(description=""" Recv binaries needed by 206 perf.data from device to binary_cache directory.""") 207 parser.add_argument('-i', '--perf_data', default='perf.data', 208 type=file_check, help=""" The path of profiling 209 data.""") 210 parser.add_argument('-l', '--local_lib_dir', type=dir_check, nargs='+', 211 help="""Path to find debug version of local shared 212 libraries used in the app.""", action='append') 213 args = parser.parse_args() 214 215 recver = GetLibFiles() 216 lib_dirs = get_arg_list(args.local_lib_dir) 217 recver.recv_binary_cache(args.perf_data, lib_dirs) 218 219 220if __name__ == '__main__': 221 main() 222