1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
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 package com.android.cts.releaseparser;
18 
19 import com.android.compatibility.common.util.ReadElf;
20 import com.android.cts.releaseparser.ReleaseProto.*;
21 import com.google.protobuf.TextFormat;
22 
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.nio.charset.Charset;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.logging.Level;
31 import java.util.logging.Logger;
32 
33 public class SoParser extends FileParser {
34     private int mBits;
35     private String mArch;
36     private List<String> mDependencies;
37     private List<String> mDynamicLoadingDependencies;
38     private ReadElf mElf;
39     private String mPackageName;
40     private ApiPackage.Builder mExternalApiPackageBuilder;
41     private HashMap<String, ApiClass.Builder> mExternalApiClassBuilderMap;
42     private ApiPackage.Builder mInternalApiPackageBuilder;
43     private AppInfo.Builder mAppInfoBuilder;
44     private boolean mParseInternalApi;
45 
SoParser(File file)46     public SoParser(File file) {
47         super(file);
48         mBits = 0;
49         mArch = null;
50         mDependencies = null;
51         mDynamicLoadingDependencies = null;
52         mAppInfoBuilder = null;
53         // default is the file name with out extenion
54         mPackageName = getFileName().split("\\.")[0];
55         // default off to avoid a large output
56         mParseInternalApi = false;
57     }
58 
59     @Override
getType()60     public Entry.EntryType getType() {
61         return Entry.EntryType.SO;
62     }
63 
64     @Override
getCodeId()65     public String getCodeId() {
66         return getFileContentId();
67     }
68 
69     @Override
getDependencies()70     public List<String> getDependencies() {
71         if (mDependencies == null) {
72             parse();
73         }
74         return mDependencies;
75     }
76 
77     @Override
getDynamicLoadingDependencies()78     public List<String> getDynamicLoadingDependencies() {
79         if (mDynamicLoadingDependencies == null) {
80             parse();
81         }
82         return mDynamicLoadingDependencies;
83     }
84 
85     @Override
getAbiBits()86     public int getAbiBits() {
87         if (mBits == 0) {
88             parse();
89         }
90         return mBits;
91     }
92 
93     @Override
getAbiArchitecture()94     public String getAbiArchitecture() {
95         if (mArch == null) {
96             parse();
97         }
98         return mArch;
99     }
100 
setPackageName(String name)101     public void setPackageName(String name) {
102         String[] subStr = name.split(File.separator);
103         mPackageName = subStr[subStr.length - 1];
104     }
105 
setParseInternalApi(boolean parseInternalApi)106     public void setParseInternalApi(boolean parseInternalApi) {
107         mParseInternalApi = parseInternalApi;
108     }
109 
getAppInfo()110     public AppInfo getAppInfo() {
111         if (mAppInfoBuilder == null) {
112             mAppInfoBuilder = AppInfo.newBuilder();
113             mAppInfoBuilder.setPackageName(mPackageName);
114             mAppInfoBuilder.addInternalApiPackages(getInternalApiPackage());
115             mAppInfoBuilder.addExternalApiPackages(getExternalApiPackage());
116         }
117         return mAppInfoBuilder.build();
118     }
119 
getExternalApiPackage()120     public ApiPackage getExternalApiPackage() {
121         if (mExternalApiPackageBuilder == null) {
122             parse();
123         }
124         return mExternalApiPackageBuilder.build();
125     }
126 
getInternalApiPackage()127     public ApiPackage getInternalApiPackage() {
128         if (mInternalApiPackageBuilder == null) {
129             parse();
130         }
131         return mInternalApiPackageBuilder.build();
132     }
133 
parse()134     private void parse() {
135         mExternalApiPackageBuilder = ApiPackage.newBuilder();
136         mExternalApiClassBuilderMap = new HashMap<String, ApiClass.Builder>();
137         mInternalApiPackageBuilder = ApiPackage.newBuilder();
138         try {
139             ReadElf mElf = ReadElf.read(getFile());
140             mBits = mElf.getBits();
141             mArch = mElf.getArchitecture();
142             mDependencies = mElf.getDynamicDependencies();
143             // Check Dynamic Loading dependencies
144             mDynamicLoadingDependencies = getDynamicLoadingDependencies(mElf);
145             parseApi(mElf.getDynSymArr());
146         } catch (Exception ex) {
147             mDependencies = super.getDependencies();
148             mDynamicLoadingDependencies = super.getDynamicLoadingDependencies();
149             mBits = -1;
150             mArch = "unknown";
151             getLogger()
152                     .log(
153                             Level.SEVERE,
154                             String.format(
155                                     "SoParser fails to parse %s. \n%s",
156                                     getFileName(), ex.getMessage()));
157             ex.printStackTrace();
158         }
159     }
160 
parseApi(ReadElf.Symbol[] symArr)161     private void parseApi(ReadElf.Symbol[] symArr) {
162         ApiClass.Builder mInternalApiClassBuilder = ApiClass.newBuilder();
163         mInternalApiClassBuilder.setName(mPackageName);
164 
165         for (ReadElf.Symbol symbol : symArr) {
166             if (symbol.isExtern()) {
167                 // Internal methods & fields
168                 if (mParseInternalApi) {
169                     // Skips reference symbols
170                     if (isInternalReferenceSymbol(symbol)) {
171                         continue;
172                     }
173                     if (symbol.type == ReadElf.Symbol.STT_OBJECT) {
174                         ApiField.Builder fieldBuilder = ApiField.newBuilder();
175                         fieldBuilder.setName(symbol.name);
176                         mInternalApiClassBuilder.addFields(fieldBuilder.build());
177                     } else {
178                         ApiMethod.Builder methodBuilder = ApiMethod.newBuilder();
179                         methodBuilder.setName(symbol.name);
180                         mInternalApiClassBuilder.addMethods(methodBuilder.build());
181                     }
182                 }
183             } else if (symbol.isGlobalUnd()) {
184                 // External dependency
185                 String className = symbol.getExternalLibFileName();
186                 ApiClass.Builder apiClassBuilder =
187                         ClassUtils.getApiClassBuilder(mExternalApiClassBuilderMap, className);
188                 if (symbol.type == ReadElf.Symbol.STT_OBJECT) {
189                     ApiField.Builder fieldBuilder = ApiField.newBuilder();
190                     fieldBuilder.setName(symbol.name);
191                     apiClassBuilder.addFields(fieldBuilder.build());
192                 } else {
193                     ApiMethod.Builder methodBuilder = ApiMethod.newBuilder();
194                     methodBuilder.setName(symbol.name);
195                     apiClassBuilder.addMethods(methodBuilder.build());
196                 }
197             }
198         }
199         if (mParseInternalApi) {
200             mInternalApiPackageBuilder.addClasses(mInternalApiClassBuilder.build());
201         }
202         ClassUtils.addAllApiClasses(mExternalApiClassBuilderMap, mExternalApiPackageBuilder);
203     }
204 
getApiClassBuilder( HashMap<String, ApiClass.Builder> apiClassBuilderMap, String name)205     private ApiClass.Builder getApiClassBuilder(
206             HashMap<String, ApiClass.Builder> apiClassBuilderMap, String name) {
207         ApiClass.Builder builder = apiClassBuilderMap.get(name);
208         if (builder == null) {
209             builder = ApiClass.newBuilder().setName(ClassUtils.getCanonicalName(name));
210             apiClassBuilderMap.put(name, builder);
211         }
212         return builder;
213     }
214 
215     // internal reference symboles
216     private static final HashMap<String, String> sInternalReferenceSymboleMap;
217 
218     static {
219         sInternalReferenceSymboleMap = new HashMap<String, String>();
220         sInternalReferenceSymboleMap.put("__bss_start", "bss");
221         sInternalReferenceSymboleMap.put("_end", "initialized data");
222         sInternalReferenceSymboleMap.put("_edata", "uninitialized data");
223     }
224 
isInternalReferenceSymbol(ReadElf.Symbol sym)225     private static boolean isInternalReferenceSymbol(ReadElf.Symbol sym) {
226         String value = sInternalReferenceSymboleMap.get(sym.name);
227         if (value == null) {
228             return false;
229         } else {
230             return true;
231         }
232     }
233 
getDynamicLoadingDependencies(ReadElf elf)234     private List<String> getDynamicLoadingDependencies(ReadElf elf) throws IOException {
235         List<String> depList = new ArrayList<>();
236         // check if it does refer to dlopen
237         if (elf.getDynamicSymbol("dlopen") != null) {
238             List<String> roStrings = elf.getRoStrings();
239             for (String str : roStrings) {
240                 // skip ".so" or less
241                 if (str.length() < 4) {
242                     continue;
243                 }
244 
245                 if (str.endsWith(".so")) {
246                     // skip itself
247                     if (str.contains(getFileName())) {
248                         continue;
249                     }
250                     if (str.contains(" ")) {
251                         continue;
252                     }
253                     if (str.contains("?")) {
254                         continue;
255                     }
256                     if (str.contains("%")) {
257                         System.err.println("ToDo getDynamicLoadingDependencies: " + str);
258                         continue;
259                     }
260                     if (str.startsWith("_")) {
261                         System.err.println("ToDo getDynamicLoadingDependencies: " + str);
262                         continue;
263                     }
264                     depList.add(str);
265                 }
266             }
267         }
268 
269         // specific for frameworks/native/opengl/libs/EGL/Loader.cpp load_system_driver()
270         if ("libEGL.so".equals(getFileName())) {
271             depList.add("libEGL*.so");
272             depList.add("libGLESv1_CM*.so");
273             depList.add("GLESv2*.so");
274         }
275 
276         return depList;
277     }
278 
279     private static final String USAGE_MESSAGE =
280             "Usage: java -jar releaseparser.jar "
281                     + ApkParser.class.getCanonicalName()
282                     + " [-options <parameter>]...\n"
283                     + "           to prase SO file meta data\n"
284                     + "Options:\n"
285                     + "\t-i PATH\t The file path of the file to be parsed.\n"
286                     + "\t-pi \t Parses internal methods and fields too. Output will be large when parsing multiple files in a release.\n"
287                     + "\t-of PATH\t The file path of the output file instead of printing to System.out.\n";
288 
main(String[] args)289     public static void main(String[] args) {
290         try {
291             ArgumentParser argParser = new ArgumentParser(args);
292             String fileName = argParser.getParameterElement("i", 0);
293             String outputFileName = argParser.getParameterElement("of", 0);
294             boolean parseInternalApi = argParser.containsOption("pi");
295 
296             File aFile = new File(fileName);
297             SoParser aParser = new SoParser(aFile);
298             aParser.setPackageName(fileName);
299             aParser.setParseInternalApi(parseInternalApi);
300 
301             if (outputFileName != null) {
302                 FileOutputStream txtOutput = new FileOutputStream(outputFileName);
303                 txtOutput.write(
304                         TextFormat.printToString(aParser.getAppInfo())
305                                 .getBytes(Charset.forName("UTF-8")));
306                 txtOutput.flush();
307                 txtOutput.close();
308             } else {
309                 System.out.println(TextFormat.printToString(aParser.getAppInfo()));
310             }
311         } catch (Exception ex) {
312             System.out.println(USAGE_MESSAGE);
313             ex.printStackTrace();
314         }
315     }
316 
getLogger()317     private static Logger getLogger() {
318         return Logger.getLogger(SoParser.class.getSimpleName());
319     }
320 }
321