• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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