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