1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright (c) 2023 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#/ 16 17 18import argparse 19import copy 20import json 21import os 22import re 23import sys 24import subprocess 25 26# the absolute path to llvm-readelf 27READELF_PATH = "" 28# the absolute path to hdc 29HDC = "" 30DEBUG = 0 31 32 33class SoParser: 34 def __init__(self, cpu): 35 self.soname_deps = {} 36 self.pulled_so = [] 37 self.visiting = [] 38 self.visited = [] 39 self.saved_so_path = [] 40 self.search_paths = "" 41 42 @staticmethod 43 def run_cmd(cmd): 44 if DEBUG: 45 print("CMD: " + cmd) 46 process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 47 try: 48 outs, errs = process.communicate(timeout=10) 49 except subprocess.TimeoutExpired: 50 process.kill() 51 print("timeout error:", " ".join(cmd), errs) 52 return outs.decode().splitlines() 53 54 @staticmethod 55 def get_soname(line): 56 m = re.search("[\S\s]+\[(\S+)\]", line) 57 if not m: 58 return "" 59 soname = m.group(1) 60 return soname 61 62 def pull_so(self, path): 63 cmd = "{} file recv {} {}".format(HDC, path, self.saved_so_path) 64 lines = self.run_cmd(cmd) 65 for line in lines: 66 if "success" in line: 67 return 1 68 return 0 69 70 def is_path_exists(self, path): 71 cmd = "{} shell ls {}".format(HDC, path) 72 lines = self.run_cmd(cmd) 73 for line in lines: 74 if "No such file or directory" in line: 75 return 0 76 return 1 77 78 def write_results(self, out_format): 79 if out_format is None: 80 self.write_dot() 81 self.write_csv() 82 self.write_json() 83 self.write_txt() 84 if out_format == "csv": 85 self.write_csv() 86 if out_format == "txt": 87 self.write_txt() 88 if out_format == "json": 89 self.write_json() 90 if out_format == "dot": 91 self.write_dot() 92 93 def write_dot(self): 94 with os.fdopen(os.open("dep.dot", os.O_CREAT | os.O_WRONLY, 0o755), 'w', encoding='utf-8') as f: 95 f.write("digraph deps {\n") 96 dot_format = "\"{}\"->\"{}\" [label={}];\n" 97 for soname, deps in self.soname_deps.items(): 98 index = 0 99 for so in deps: 100 index += 1 101 f.write(dot_format.format(soname, so, index)) 102 f.write("}\n") 103 f.close() 104 self.run_cmd("dot -Tsvg -o dep.svg dep.dot") 105 106 def write_json(self): 107 with os.fdopen(os.open("dep.json", os.O_CREAT | os.O_WRONLY, 0o755), 'w', encoding='utf-8') as f: 108 to_json = json.dumps(self.soname_deps) 109 f.write(to_json) 110 f.close() 111 112 def write_txt(self): 113 with os.fdopen(os.open("dep.ext", os.O_CREAT | os.O_WRONLY, 0o755), 'w', encoding='utf-8') as f: 114 for soname, deps in self.soname_deps.items(): 115 f.write("{}:{}\n".format(soname, ",".join(deps))) 116 f.close() 117 118 def write_csv(self): 119 with os.fdopen(os.open("dep.csv", os.O_CREAT | os.O_WRONLY, 0o755), 'w', encoding='utf-8') as f: 120 for soname, deps in self.soname_deps.items(): 121 f.write("{},{}\n".format(soname, ",".join(deps))) 122 f.close() 123 124 def read_soinfo(self, so_path_local): 125 lines = self.run_cmd("{} -dW {}".format(READELF_PATH, so_path_local)) 126 deps = [] 127 entry_so = "" 128 for line in lines: 129 if "Library soname" in line: 130 entry_so = self.get_soname(line) 131 if "NEEDED" in line: 132 deps.append(self.get_soname(line)) 133 134 self.soname_deps[entry_so] = deps 135 return deps 136 137 def find_so(self, so_name): 138 if so_name in self.pulled_so: 139 print(so_name + " already saved in " + self.saved_so_path) 140 return 1 141 for path in self.search_paths: 142 abs_path = os.path.join(path, so_name) 143 if self.is_path_exists(abs_path): 144 if self.pull_so(abs_path) == 0: 145 print("pull so failed!") 146 exit(-1) 147 self.pulled_so.append(so_name) 148 return 1 149 return 0 150 151 def bfs(self, so_name, skip_pull_so): 152 self.visiting.append(so_name) 153 index = 0 154 while len(self.visiting) > 0: 155 cur = self.visiting.pop(0) 156 if cur in self.visited: 157 continue 158 index += 1 159 if not skip_pull_so: 160 if self.find_so(cur) == 0: 161 print("can't find {}".format(cur)) 162 exit(-1) 163 164 deps = self.read_soinfo(self.saved_so_path + cur) 165 print("\033[1;33m index:{} so:{} deps:[{}]\033[0m \n".format(index, cur, ",".join(deps))) 166 if deps: 167 self.visiting.extend(deps) 168 self.visited.append(cur) 169 170 171def main(): 172 parser = argparse.ArgumentParser() 173 parser.add_argument('-i', '--input', type=str, required=True, help='Input so name that you want to show its deps.') 174 parser.add_argument('-f', '--format', type=str, help='Set output file format: csv, json, dot, txt, default is all.') 175 parser.add_argument('-s', '--skip-pull-so', action='store_true', help='You can skip pull so from device if you have done it.') 176 parser.add_argument('-c', '--cpu', type=str, required=True, help='Set device type: 64 or 32 system.') 177 parser.add_argument('-p', '--path', type=str, default="paths.json", help='Set so search paths in your device.') 178 args = parser.parse_args() 179 180 so_parser = SoParser(args.cpu) 181 with os.fdopen(os.open(args.path, os.O_RDONLY, 0o755), 'r', encoding='utf-8') as f: 182 so_parser.search_paths = json.load(f)[args.cpu] 183 so_parser.saved_so_path = os.path.join(os.getcwd(), "saved/") 184 if not args.skip_pull_so: 185 so_parser.run_cmd("rm -rf {} ".format(so_parser.saved_so_path)) 186 so_parser.run_cmd("mkdir {}".format(so_parser.saved_so_path)) 187 so_parser.bfs(args.input, args.skip_pull_so) 188 so_parser.write_results(args.format) 189 190 191if __name__ == "__main__": 192 sys.exit(main()) 193