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.compatibility.common.util;
18 
19 import org.junit.Test;
20 import org.junit.runner.RunWith;
21 import org.junit.runners.JUnit4;
22 
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.FileReader;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 import static org.junit.Assert.*;
34 
35 /**
36  * Tests if {@link ReadElf} parses Executable and Linkable Format files properly.
37  *
38  * <p>These tests validate content, parsed by {@link ReadElf} is the same with the golden sample
39  * files. The golden sample files are the outputs from Linux cmd: readelf -a elf-file
40  *
41  * <p>
42  */
43 @RunWith(JUnit4.class)
44 public class ReadElfTest {
45     private static final String THIS_CLASS = "com.android.compatibility.common.util.ReadElfTest";
46     private static final String TEST_SO_ARM32B = "arm32_libdl.so";
47     private static final String TEST_SO_ARM32B_READELF = "arm32_libdl.txt";
48     private static final String TEST_SO_ARM64B = "arm64_libdl.so";
49     private static final String TEST_SO_ARM64B_READELF = "arm64_libdl.txt";
50     private static final String TEST_EXE_X8664B = "x86app_process64";
51     private static final String TEST_EXE_X8664B_READELF = "x86app_process64.txt";
52     private static final String TEST_EXE_X8632B = "x86app_process32";
53     private static final String TEST_EXE_X8632B_READELF = "x86app_process32.txt";
54 
55     /**
56      * Test {@link ReadElf} for an ARM 32-bit Shared Object
57      *
58      * @throws Exception
59      */
60     @Test
testReadElfArm32b()61     public void testReadElfArm32b() throws Exception {
62         checkReadElf(TEST_SO_ARM32B, TEST_SO_ARM32B_READELF, ReadElf.ARCH_ARM, 32, ReadElf.ET_DYN);
63     }
64 
65     /**
66      * Test {@link ReadElf} for an ARM 64-bit Shared Object
67      *
68      * @throws Exception
69      */
70     @Test
testReadElfArm64b()71     public void testReadElfArm64b() throws Exception {
72         checkReadElf(TEST_SO_ARM64B, TEST_SO_ARM64B_READELF, ReadElf.ARCH_ARM, 64, ReadElf.ET_DYN);
73     }
74 
75     /**
76      * Test {@link ReadElf} for an x86 32-bit Executable
77      *
78      * @throws Exception
79      */
80     @Test
testReadElfX8632b()81     public void testReadElfX8632b() throws Exception {
82         checkReadElf(
83                 TEST_EXE_X8632B, TEST_EXE_X8632B_READELF, ReadElf.ARCH_X86, 32, ReadElf.ET_DYN);
84     }
85 
86     /**
87      * Test {@link ReadElf} for an x86 64-bit Executable
88      *
89      * @throws Exception
90      */
91     @Test
testReadElfX8664b()92     public void testReadElfX8664b() throws Exception {
93         checkReadElf(
94                 TEST_EXE_X8664B, TEST_EXE_X8664B_READELF, ReadElf.ARCH_X86, 64, ReadElf.ET_DYN);
95     }
96 
97     /**
98      * Compares {@link ReadElf} returns same results with Linux readelf cmd on the same ELF file
99      *
100      * @param elfFileName the name of an ELF file in Resource to be checked
101      * @param elfOutputFileName the name of the golden sample file in Resource
102      * @param bits the expected bits of the ELF file
103      * @param arch the expected Instruction Set Architecture of the ELF file
104      * @param type the expected object file type of the ELF file
105      */
checkReadElf( String elfFileName, String elfOutputFileName, String arch, int bits, int type)106     private void checkReadElf(
107             String elfFileName, String elfOutputFileName, String arch, int bits, int type)
108             throws Exception {
109         File targetFile = getResrouceFile(elfFileName);
110         assertEquals("ReadElf.isElf() " + elfFileName, true, ReadElf.isElf(targetFile));
111         ReadElf elf = ReadElf.read(targetFile);
112         assertEquals("getBits() ", bits, elf.getBits());
113         assertEquals("getArchitecture() ", arch, elf.getArchitecture());
114         assertEquals("isDynamic() ", true, elf.isDynamic());
115         assertEquals("getType() ", type, elf.getType());
116 
117         File elfOutputFile = getResrouceFile(elfOutputFileName);
118         assertEquals("ReadElf.isElf() " + elfOutputFileName, false, ReadElf.isElf(elfOutputFile));
119 
120         final ReadElf.Symbol[] dynSymbolArr = elf.getDynSymArr();
121         chkDynSymbol(elfOutputFile, dynSymbolArr);
122 
123         assertEquals(
124                 "ReadElf.getDynamicDependencies() " + elfFileName,
125                 getDynamicDependencies(elfOutputFile),
126                 elf.getDynamicDependencies());
127 
128         assertEquals(
129                 "ReadElf.getRoStrings() " + elfFileName,
130                 getRoStrings(elfOutputFile),
131                 elf.getRoStrings());
132     }
133 
134     /**
135      * Gets a list of needed libraries from a Linux readelf cmd output file
136      *
137      * @param elfOutputFileName a {@link File} object of an golden sample file
138      * @return a name list of needed libraries
139      */
getDynamicDependencies(File elfOutputFile)140     private List<String> getDynamicDependencies(File elfOutputFile) throws IOException {
141         List<String> result = new ArrayList<>();
142 
143         FileReader fileReader = new FileReader(elfOutputFile);
144         BufferedReader buffReader = new BufferedReader(fileReader);
145 
146         String line;
147         boolean keepGoing = true;
148         while ((line = buffReader.readLine()) != null && keepGoing) {
149             // readelf output as: Dynamic section at offset 0xfdf0 contains 17 entries:
150             if (line.startsWith("Dynamic section")) {
151                 String dsLine;
152                 while ((dsLine = buffReader.readLine()) != null) {
153                     String trimLine = dsLine.trim();
154                     if (trimLine.isEmpty()) {
155                         // End of the block
156                         keepGoing = false;
157                         break;
158                     }
159 
160                     // 0x0000000000000001 (NEEDED)             Shared library: [ld-android.so]
161                     if (trimLine.contains("1 (NEEDED)")) {
162                         result.add(
163                                 trimLine.substring(
164                                         trimLine.indexOf("[") + 1, trimLine.indexOf("]")));
165                     }
166                 }
167             }
168         }
169         fileReader.close();
170         return result;
171     }
172 
173     /**
174      * Checks if all Dynamic Symbols in a golden sample file are in the symbol array
175      *
176      * @param targetFile a {@link File} object of an golden sample file
177      * @param dynSymbolArr a Dynamic Symbol array to be validated
178      */
chkDynSymbol(File targetFile, ReadElf.Symbol[] dynSymbolArr)179     private void chkDynSymbol(File targetFile, ReadElf.Symbol[] dynSymbolArr) throws IOException {
180         FileReader fileReader = new FileReader(targetFile);
181         BufferedReader buffReader = new BufferedReader(fileReader);
182 
183         String line;
184         boolean keepGoing = true;
185         while ((line = buffReader.readLine()) != null && keepGoing) {
186             // readelf output as: Symbol table '.dynsym' contains 44 entries:
187             if (line.startsWith("Symbol table '.dynsym'")) {
188                 // Skip the header:   Num:    Value  Size Type    Bind   Vis      Ndx Name
189                 buffReader.readLine();
190                 // Skip the 1st line:     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
191                 buffReader.readLine();
192 
193                 int i = 1;
194 
195                 String dsLine;
196                 while ((dsLine = buffReader.readLine()) != null) {
197                     // readelf output as:
198                     //    20: 0000000000000ff8    24 FUNC    WEAK   DEFAULT    9
199                     // android_init_anonymous_na@@LIBC_PLATFORM
200                     String trimLine = dsLine.trim();
201                     if (trimLine.isEmpty()) {
202                         // End of the block
203                         keepGoing = false;
204                         break;
205                     }
206 
207                     // Removes tailing (x) for an executable
208                     //      1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@LIBC (2)
209                     int idx = trimLine.indexOf("(");
210                     if (idx > 0) {
211                         trimLine = trimLine.substring(0, idx);
212                     }
213 
214                     String phases[] = trimLine.split("\\s+");
215                     String symName = dynSymbolArr[i].name;
216                     String name = phases[phases.length - 1].split("@")[0];
217                     // readelf may truncate a long name
218                     assertTrue(
219                             String.format("chkDynSymbol name %d: %s vs %s", i, symName, name),
220                             symName.startsWith(name));
221                     assertEquals("chkDynSymbol type :", dynSymbolArr[i].toType(), phases[3]);
222                     assertEquals("chkDynSymbol bind :", dynSymbolArr[i].toBind(), phases[4]);
223                     assertEquals("chkDynSymbol ndx :", dynSymbolArr[i].toShndx(), phases[6]);
224                     i++;
225                 }
226             }
227         }
228         fileReader.close();
229     }
230 
231     /**
232      * Gets a list of Read Only Strings from a Linux readelf -p .rodata cmd output file
233      *
234      * @param elfOutputFileName a {@link File} object of an golden sample file
235      * @return a list of RO Strings
236      */
getRoStrings(File elfOutputFile)237     private List<String> getRoStrings(File elfOutputFile) throws IOException {
238         List<String> result = new ArrayList<>();
239 
240         FileReader fileReader = new FileReader(elfOutputFile);
241         BufferedReader buffReader = new BufferedReader(fileReader);
242 
243         String line;
244         boolean keepGoing = true;
245         while ((line = buffReader.readLine()) != null && keepGoing) {
246             // readelf output as: String dump of section '.rodata':
247             if (line.startsWith("String dump of section '.rodata':")) {
248                 String dsLine;
249                 while ((dsLine = buffReader.readLine()) != null) {
250                     String trimLine = dsLine.trim();
251                     if (trimLine.isEmpty()) {
252                         // End of the block
253                         keepGoing = false;
254                         break;
255                     }
256 
257                     //  [   108]  Error: no class name or --zygote supplied.^J
258                     if (trimLine.contains("[")) {
259                         result.add(
260                                 trimLine.substring(trimLine.indexOf("]") + 1, trimLine.length())
261                                         .trim()
262                                         .replace("^J", "\n"));
263                     }
264                 }
265             }
266         }
267         fileReader.close();
268         return result;
269     }
270 
271     /**
272      * Reads a file from Resource and write to a tmp file
273      *
274      * @param fileName the name of a file in Resource
275      * @return the File object of a tmp file
276      */
getResrouceFile(String fileName)277     private File getResrouceFile(String fileName) throws IOException {
278         File tempFile = File.createTempFile(fileName, "tmp");
279         tempFile.deleteOnExit();
280         try (InputStream input = openResourceAsStream(fileName);
281              OutputStream output = new FileOutputStream(tempFile)) {
282             byte[] buffer = new byte[4096];
283             int length;
284             while ((length = input.read(buffer)) > 0) {
285                 output.write(buffer, 0, length);
286             }
287         }
288         return tempFile;
289     }
290 
291     /**
292      * Gets an InputStrem of a file from Resource
293      *
294      * @param fileName the name of a file in Resrouce
295      * @return the (@link InputStream} object of the file
296      */
openResourceAsStream(String fileName)297     private InputStream openResourceAsStream(String fileName) {
298         InputStream input = getClass().getResourceAsStream("/" + fileName);
299         assertNotNull(input);
300         return input;
301     }
302 }